gangtise-openapi-cli 0.11.0 → 0.12.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.
@@ -1,76 +1,79 @@
1
1
  export const ENDPOINTS = {
2
- authLogin: {
2
+ // ─── auth ───
3
+ "auth.login": {
3
4
  key: "auth.login",
4
5
  method: "POST",
5
6
  path: "/application/auth/oauth/open/loginV2",
6
7
  kind: "json",
7
8
  description: "Get access token",
8
9
  },
9
- lookupResearchAreas: {
10
+ // ─── lookup (served from local data, not HTTP) ───
11
+ "lookup.research-areas.list": {
10
12
  key: "lookup.research-areas.list",
11
13
  method: "GET",
12
14
  path: "/guide/research-area-local",
13
15
  kind: "json",
14
16
  description: "List research areas from local docs",
15
17
  },
16
- lookupBrokerOrgs: {
18
+ "lookup.broker-orgs.list": {
17
19
  key: "lookup.broker-orgs.list",
18
20
  method: "GET",
19
21
  path: "/guide/broker-orgs-local",
20
22
  kind: "json",
21
23
  description: "List broker orgs from local docs",
22
24
  },
23
- lookupMeetingOrgs: {
25
+ "lookup.meeting-orgs.list": {
24
26
  key: "lookup.meeting-orgs.list",
25
27
  method: "GET",
26
28
  path: "/guide/meeting-orgs-local",
27
29
  kind: "json",
28
30
  description: "List meeting orgs from local docs",
29
31
  },
30
- lookupIndustries: {
32
+ "lookup.industries.list": {
31
33
  key: "lookup.industries.list",
32
34
  method: "GET",
33
35
  path: "/guide/industries-local",
34
36
  kind: "json",
35
37
  description: "List industries from local docs",
36
38
  },
37
- lookupRegions: {
39
+ "lookup.regions.list": {
38
40
  key: "lookup.regions.list",
39
41
  method: "GET",
40
42
  path: "/guide/regions-local",
41
43
  kind: "json",
42
44
  description: "List regions from local docs",
43
45
  },
44
- lookupAnnouncementCategories: {
46
+ "lookup.announcement-categories.list": {
45
47
  key: "lookup.announcement-categories.list",
46
48
  method: "GET",
47
49
  path: "/guide/announcement-categories-local",
48
50
  kind: "json",
49
51
  description: "List announcement categories from local docs",
50
52
  },
51
- lookupIndustryCodes: {
53
+ "lookup.industry-codes.list": {
52
54
  key: "lookup.industry-codes.list",
53
55
  method: "GET",
54
56
  path: "/guide/industry-codes-local",
55
57
  kind: "json",
56
58
  description: "List Shenwan industry codes from local docs",
57
59
  },
58
- lookupThemeIds: {
60
+ "lookup.theme-ids.list": {
59
61
  key: "lookup.theme-ids.list",
60
62
  method: "GET",
61
63
  path: "/guide/theme-ids-local",
62
64
  kind: "json",
63
65
  description: "List theme IDs from local docs",
64
66
  },
65
- insightOpinionList: {
67
+ // ─── insight ───
68
+ "insight.opinion.list": {
66
69
  key: "insight.opinion.list",
67
70
  method: "POST",
68
71
  path: "/application/open-insight/chief-opinion/getList",
69
72
  kind: "json",
70
- description: "List chief opinions",
73
+ description: "List domestic institution chief opinions",
71
74
  pagination: { enabled: true, maxPageSize: 50 },
72
75
  },
73
- insightSummaryList: {
76
+ "insight.summary.list": {
74
77
  key: "insight.summary.list",
75
78
  method: "POST",
76
79
  path: "/application/open-insight/summary/v2/getList",
@@ -78,14 +81,14 @@ export const ENDPOINTS = {
78
81
  description: "List summaries",
79
82
  pagination: { enabled: true, maxPageSize: 50 },
80
83
  },
81
- insightSummaryDownload: {
84
+ "insight.summary.download": {
82
85
  key: "insight.summary.download",
83
86
  method: "GET",
84
87
  path: "/application/open-insight/summary/v2/download/file",
85
88
  kind: "download",
86
89
  description: "Download summary file",
87
90
  },
88
- insightRoadshowList: {
91
+ "insight.roadshow.list": {
89
92
  key: "insight.roadshow.list",
90
93
  method: "POST",
91
94
  path: "/application/open-insight/schedule/roadshow/getList",
@@ -93,7 +96,7 @@ export const ENDPOINTS = {
93
96
  description: "List roadshows",
94
97
  pagination: { enabled: true, maxPageSize: 50 },
95
98
  },
96
- insightSiteVisitList: {
99
+ "insight.site-visit.list": {
97
100
  key: "insight.site-visit.list",
98
101
  method: "POST",
99
102
  path: "/application/open-insight/schedule/site-visit/getList",
@@ -101,7 +104,7 @@ export const ENDPOINTS = {
101
104
  description: "List site visits",
102
105
  pagination: { enabled: true, maxPageSize: 50 },
103
106
  },
104
- insightStrategyList: {
107
+ "insight.strategy.list": {
105
108
  key: "insight.strategy.list",
106
109
  method: "POST",
107
110
  path: "/application/open-insight/schedule/strategy-meeting/getList",
@@ -109,7 +112,7 @@ export const ENDPOINTS = {
109
112
  description: "List strategy meetings",
110
113
  pagination: { enabled: true, maxPageSize: 50 },
111
114
  },
112
- insightForumList: {
115
+ "insight.forum.list": {
113
116
  key: "insight.forum.list",
114
117
  method: "POST",
115
118
  path: "/application/open-insight/schedule/forum/getList",
@@ -117,7 +120,7 @@ export const ENDPOINTS = {
117
120
  description: "List forums",
118
121
  pagination: { enabled: true, maxPageSize: 50 },
119
122
  },
120
- insightResearchList: {
123
+ "insight.research.list": {
121
124
  key: "insight.research.list",
122
125
  method: "POST",
123
126
  path: "/application/open-insight/broker-report/getList",
@@ -125,14 +128,14 @@ export const ENDPOINTS = {
125
128
  description: "List broker research reports",
126
129
  pagination: { enabled: true, maxPageSize: 50 },
127
130
  },
128
- insightResearchDownload: {
131
+ "insight.research.download": {
129
132
  key: "insight.research.download",
130
133
  method: "GET",
131
134
  path: "/application/open-insight/broker-report/download/file",
132
135
  kind: "download",
133
136
  description: "Download broker research report",
134
137
  },
135
- insightForeignReportList: {
138
+ "insight.foreign-report.list": {
136
139
  key: "insight.foreign-report.list",
137
140
  method: "POST",
138
141
  path: "/application/open-insight/foreign-report/getList",
@@ -140,113 +143,183 @@ export const ENDPOINTS = {
140
143
  description: "List foreign reports",
141
144
  pagination: { enabled: true, maxPageSize: 50 },
142
145
  },
143
- insightForeignReportDownload: {
146
+ "insight.foreign-report.download": {
144
147
  key: "insight.foreign-report.download",
145
148
  method: "GET",
146
149
  path: "/application/open-insight/foreign-report/download/file",
147
150
  kind: "download",
148
151
  description: "Download foreign report",
149
152
  },
150
- insightAnnouncementList: {
153
+ "insight.announcement.list": {
151
154
  key: "insight.announcement.list",
152
155
  method: "POST",
153
156
  path: "/application/open-insight/announcement/getList",
154
157
  kind: "json",
155
- description: "List announcements",
158
+ description: "List A-share announcements",
156
159
  pagination: { enabled: true, maxPageSize: 50 },
157
160
  },
158
- insightAnnouncementDownload: {
161
+ "insight.announcement.download": {
159
162
  key: "insight.announcement.download",
160
163
  method: "GET",
161
164
  path: "/application/open-insight/announcement/download/file",
162
165
  kind: "download",
163
- description: "Download announcement file",
166
+ description: "Download A-share announcement file",
164
167
  },
165
- quoteDayKline: {
168
+ "insight.announcement-hk.list": {
169
+ key: "insight.announcement-hk.list",
170
+ method: "POST",
171
+ path: "/application/open-insight/announcement-hk/getList",
172
+ kind: "json",
173
+ description: "List HK announcements",
174
+ pagination: { enabled: true, maxPageSize: 50 },
175
+ },
176
+ "insight.announcement-hk.download": {
177
+ key: "insight.announcement-hk.download",
178
+ method: "GET",
179
+ path: "/application/open-insight/announcement-hk/download/file",
180
+ kind: "download",
181
+ description: "Download HK announcement file",
182
+ },
183
+ "insight.foreign-opinion.list": {
184
+ key: "insight.foreign-opinion.list",
185
+ method: "POST",
186
+ path: "/application/open-insight/foreign-opinion/getList",
187
+ kind: "json",
188
+ description: "List foreign institution opinions",
189
+ pagination: { enabled: true, maxPageSize: 50 },
190
+ },
191
+ "insight.independent-opinion.list": {
192
+ key: "insight.independent-opinion.list",
193
+ method: "POST",
194
+ path: "/application/open-insight/independent-opinion/getList",
195
+ kind: "json",
196
+ description: "List foreign independent analyst opinions",
197
+ pagination: { enabled: true, maxPageSize: 50 },
198
+ },
199
+ "insight.independent-opinion.download": {
200
+ key: "insight.independent-opinion.download",
201
+ method: "GET",
202
+ path: "/application/open-insight/independent-opinion/download/file",
203
+ kind: "download",
204
+ description: "Download foreign independent opinion file",
205
+ },
206
+ // ─── reference ───
207
+ "reference.securities-search": {
208
+ key: "reference.securities-search",
209
+ method: "POST",
210
+ path: "/application/open-reference/securities/search",
211
+ kind: "json",
212
+ description: "Search GTS codes (securities)",
213
+ },
214
+ // ─── quote ───
215
+ "quote.day-kline": {
166
216
  key: "quote.day-kline",
167
217
  method: "POST",
168
218
  path: "/application/open-quote/kline/daily",
169
219
  kind: "json",
170
220
  description: "Query A-share daily kline (SH/SZ/BJ)",
171
221
  },
172
- quoteDayKlineHk: {
222
+ "quote.day-kline-hk": {
173
223
  key: "quote.day-kline-hk",
174
224
  method: "POST",
175
225
  path: "/application/open-quote/kline-hk/daily",
176
226
  kind: "json",
177
227
  description: "Query HK stock daily kline (HK)",
178
228
  },
179
- quoteIndexDayKline: {
229
+ "quote.index-day-kline": {
180
230
  key: "quote.index-day-kline",
181
231
  method: "POST",
182
232
  path: "/application/open-quote/index/kline/daily",
183
233
  kind: "json",
184
234
  description: "Query SH/SZ/BJ index daily kline",
185
235
  },
186
- fundamentalIncomeStatement: {
236
+ "quote.minute-kline": {
237
+ key: "quote.minute-kline",
238
+ method: "POST",
239
+ path: "/application/open-quote/kline/minute",
240
+ kind: "json",
241
+ description: "Query A-share minute kline (SH/SZ/BJ)",
242
+ },
243
+ // ─── fundamental ───
244
+ "fundamental.income-statement": {
187
245
  key: "fundamental.income-statement",
188
246
  method: "POST",
189
247
  path: "/application/open-fundamental/financial-report/income-statement/accumulated",
190
248
  kind: "json",
191
249
  description: "Query income statement (accumulated)",
192
250
  },
193
- fundamentalBalanceSheet: {
251
+ "fundamental.income-statement-quarterly": {
252
+ key: "fundamental.income-statement-quarterly",
253
+ method: "POST",
254
+ path: "/application/open-fundamental/financial-report/income-statement/quarterly",
255
+ kind: "json",
256
+ description: "Query income statement (quarterly)",
257
+ },
258
+ "fundamental.balance-sheet": {
194
259
  key: "fundamental.balance-sheet",
195
260
  method: "POST",
196
261
  path: "/application/open-fundamental/financial-report/balance-sheet/accumulated",
197
262
  kind: "json",
198
263
  description: "Query balance sheet (accumulated)",
199
264
  },
200
- fundamentalCashFlow: {
265
+ "fundamental.cash-flow": {
201
266
  key: "fundamental.cash-flow",
202
267
  method: "POST",
203
268
  path: "/application/open-fundamental/financial-report/cash-flow-statement/accumulated",
204
269
  kind: "json",
205
270
  description: "Query cash flow statement (accumulated)",
206
271
  },
207
- fundamentalMainBusiness: {
208
- key: "fundamental.main-business",
272
+ "fundamental.cash-flow-quarterly": {
273
+ key: "fundamental.cash-flow-quarterly",
209
274
  method: "POST",
210
- path: "/application/open-fundamental/main-business",
275
+ path: "/application/open-fundamental/financial-report/cash-flow-statement/quarterly",
211
276
  kind: "json",
212
- description: "Query main business composition",
277
+ description: "Query cash flow statement (quarterly)",
213
278
  },
214
- fundamentalEarningForecast: {
215
- key: "fundamental.earning-forecast",
279
+ "fundamental.main-business": {
280
+ key: "fundamental.main-business",
216
281
  method: "POST",
217
- path: "/application/open-fundamental/earning-forecast",
282
+ path: "/application/open-fundamental/main-business",
218
283
  kind: "json",
219
- description: "Query earning forecast (consensus estimates)",
284
+ description: "Query main business composition",
220
285
  },
221
- fundamentalValuationAnalysis: {
286
+ "fundamental.valuation-analysis": {
222
287
  key: "fundamental.valuation-analysis",
223
288
  method: "POST",
224
289
  path: "/application/open-fundamental/valuation-analysis",
225
290
  kind: "json",
226
291
  description: "Query valuation analysis",
227
292
  },
228
- fundamentalTopHolders: {
293
+ "fundamental.top-holders": {
229
294
  key: "fundamental.top-holders",
230
295
  method: "POST",
231
296
  path: "/application/open-fundamental/capital-structure/top-holders",
232
297
  kind: "json",
233
298
  description: "Query top holders (top10 / top10 float)",
234
299
  },
235
- aiKnowledgeBatch: {
300
+ "fundamental.earning-forecast": {
301
+ key: "fundamental.earning-forecast",
302
+ method: "POST",
303
+ path: "/application/open-fundamental/earning-forecast",
304
+ kind: "json",
305
+ description: "Query earning forecast (consensus estimates)",
306
+ },
307
+ // ─── ai ───
308
+ "ai.knowledge-batch": {
236
309
  key: "ai.knowledge-batch",
237
310
  method: "POST",
238
311
  path: "/application/open-data/ai/search/knowledge/batch",
239
312
  kind: "json",
240
313
  description: "Batch knowledge search",
241
314
  },
242
- aiKnowledgeResource: {
315
+ "ai.knowledge-resource.download": {
243
316
  key: "ai.knowledge-resource.download",
244
317
  method: "GET",
245
318
  path: "/application/open-data/ai/resource/download",
246
319
  kind: "download",
247
320
  description: "Download knowledge resource",
248
321
  },
249
- aiSecurityClue: {
322
+ "ai.security-clue.list": {
250
323
  key: "ai.security-clue.list",
251
324
  method: "POST",
252
325
  path: "/application/open-ai/security-clue/getList",
@@ -254,56 +327,56 @@ export const ENDPOINTS = {
254
327
  description: "List security clues",
255
328
  pagination: { enabled: true, maxPageSize: 500 },
256
329
  },
257
- aiOnePager: {
330
+ "ai.one-pager": {
258
331
  key: "ai.one-pager",
259
332
  method: "POST",
260
333
  path: "/application/open-ai/agent/one-pager",
261
334
  kind: "json",
262
335
  description: "Generate one pager",
263
336
  },
264
- aiInvestmentLogic: {
337
+ "ai.investment-logic": {
265
338
  key: "ai.investment-logic",
266
339
  method: "POST",
267
340
  path: "/application/open-ai/agent/investment-logic",
268
341
  kind: "json",
269
342
  description: "Generate investment logic",
270
343
  },
271
- aiPeerComparison: {
344
+ "ai.peer-comparison": {
272
345
  key: "ai.peer-comparison",
273
346
  method: "POST",
274
347
  path: "/application/open-ai/agent/peer-comparison",
275
348
  kind: "json",
276
349
  description: "Generate peer comparison",
277
350
  },
278
- aiEarningsReviewGetId: {
351
+ "ai.earnings-review.get-id": {
279
352
  key: "ai.earnings-review.get-id",
280
353
  method: "POST",
281
354
  path: "/application/open-ai/agent/earnings-review-getid",
282
355
  kind: "json",
283
356
  description: "Get earnings review ID",
284
357
  },
285
- aiEarningsReviewGetContent: {
358
+ "ai.earnings-review.get-content": {
286
359
  key: "ai.earnings-review.get-content",
287
360
  method: "POST",
288
361
  path: "/application/open-ai/agent/earnings-review-getcontent",
289
362
  kind: "json",
290
363
  description: "Get earnings review content",
291
364
  },
292
- aiThemeTracking: {
365
+ "ai.theme-tracking": {
293
366
  key: "ai.theme-tracking",
294
367
  method: "POST",
295
368
  path: "/application/open-ai/agent/theme-tracking",
296
369
  kind: "json",
297
370
  description: "Get theme tracking daily report",
298
371
  },
299
- aiResearchOutline: {
372
+ "ai.research-outline": {
300
373
  key: "ai.research-outline",
301
374
  method: "POST",
302
375
  path: "/application/open-ai/agent/research-outline",
303
376
  kind: "json",
304
377
  description: "Get company research outline",
305
378
  },
306
- aiHotTopic: {
379
+ "ai.hot-topic": {
307
380
  key: "ai.hot-topic",
308
381
  method: "POST",
309
382
  path: "/application/open-ai/hot-topic/getList",
@@ -311,56 +384,36 @@ export const ENDPOINTS = {
311
384
  description: "List hot topic reports",
312
385
  pagination: { enabled: true, maxPageSize: 20 },
313
386
  },
314
- aiManagementDiscussAnnouncement: {
387
+ "ai.management-discuss-announcement": {
315
388
  key: "ai.management-discuss-announcement",
316
389
  method: "POST",
317
390
  path: "/application/open-ai/management-discuss/from-announcement",
318
391
  kind: "json",
319
392
  description: "Management discussion from financial reports (half-year/annual)",
320
393
  },
321
- aiManagementDiscussEarningsCall: {
394
+ "ai.management-discuss-earnings-call": {
322
395
  key: "ai.management-discuss-earnings-call",
323
396
  method: "POST",
324
397
  path: "/application/open-ai/management-discuss/from-earningsCall",
325
398
  kind: "json",
326
399
  description: "Management discussion from earnings calls",
327
400
  },
328
- aiViewpointDebateGetId: {
401
+ "ai.viewpoint-debate.get-id": {
329
402
  key: "ai.viewpoint-debate.get-id",
330
403
  method: "POST",
331
404
  path: "/application/open-ai/agent/viewpoint-debate-getid",
332
405
  kind: "json",
333
406
  description: "Get viewpoint debate ID",
334
407
  },
335
- aiViewpointDebateGetContent: {
408
+ "ai.viewpoint-debate.get-content": {
336
409
  key: "ai.viewpoint-debate.get-content",
337
410
  method: "POST",
338
411
  path: "/application/open-ai/agent/viewpoint-debate-getcontent",
339
412
  kind: "json",
340
413
  description: "Get viewpoint debate content",
341
414
  },
342
- quoteMinuteKline: {
343
- key: "quote.minute-kline",
344
- method: "POST",
345
- path: "/application/open-quote/kline/minute",
346
- kind: "json",
347
- description: "Query A-share minute kline (SH/SZ/BJ)",
348
- },
349
- fundamentalIncomeStatementQuarterly: {
350
- key: "fundamental.income-statement-quarterly",
351
- method: "POST",
352
- path: "/application/open-fundamental/financial-report/income-statement/quarterly",
353
- kind: "json",
354
- description: "Query income statement (quarterly)",
355
- },
356
- fundamentalCashFlowQuarterly: {
357
- key: "fundamental.cash-flow-quarterly",
358
- method: "POST",
359
- path: "/application/open-fundamental/financial-report/cash-flow-statement/quarterly",
360
- kind: "json",
361
- description: "Query cash flow statement (quarterly)",
362
- },
363
- vaultDriveList: {
415
+ // ─── vault ───
416
+ "vault.drive.list": {
364
417
  key: "vault.drive.list",
365
418
  method: "POST",
366
419
  path: "/application/open-vault/drive/getList",
@@ -368,14 +421,14 @@ export const ENDPOINTS = {
368
421
  description: "List vault drive files",
369
422
  pagination: { enabled: true, maxPageSize: 50 },
370
423
  },
371
- vaultDriveDownload: {
424
+ "vault.drive.download": {
372
425
  key: "vault.drive.download",
373
426
  method: "GET",
374
427
  path: "/application/open-vault/drive/download/file",
375
428
  kind: "download",
376
429
  description: "Download vault drive file",
377
430
  },
378
- vaultRecordList: {
431
+ "vault.record.list": {
379
432
  key: "vault.record.list",
380
433
  method: "POST",
381
434
  path: "/application/open-vault/record/getList",
@@ -383,14 +436,14 @@ export const ENDPOINTS = {
383
436
  description: "List voice recording transcriptions",
384
437
  pagination: { enabled: true, maxPageSize: 50 },
385
438
  },
386
- vaultRecordDownload: {
439
+ "vault.record.download": {
387
440
  key: "vault.record.download",
388
441
  method: "GET",
389
442
  path: "/application/open-vault/record/download/file",
390
443
  kind: "download",
391
444
  description: "Download voice recording transcription file",
392
445
  },
393
- vaultMyConferenceList: {
446
+ "vault.my-conference.list": {
394
447
  key: "vault.my-conference.list",
395
448
  method: "POST",
396
449
  path: "/application/open-vault/my-conference/getList",
@@ -398,14 +451,14 @@ export const ENDPOINTS = {
398
451
  description: "List my conferences",
399
452
  pagination: { enabled: true, maxPageSize: 50 },
400
453
  },
401
- vaultMyConferenceDownload: {
454
+ "vault.my-conference.download": {
402
455
  key: "vault.my-conference.download",
403
456
  method: "GET",
404
457
  path: "/application/open-vault/my-conference/download/file",
405
458
  kind: "download",
406
459
  description: "Download my conference resource",
407
460
  },
408
- vaultWechatMessageList: {
461
+ "vault.wechat-message.list": {
409
462
  key: "vault.wechat-message.list",
410
463
  method: "POST",
411
464
  path: "/application/open-vault/wechatgroupmsg/list",
@@ -413,7 +466,7 @@ export const ENDPOINTS = {
413
466
  description: "List WeChat group messages",
414
467
  pagination: { enabled: true, maxPageSize: 50 },
415
468
  },
416
- vaultWechatChatroomList: {
469
+ "vault.wechat-chatroom.list": {
417
470
  key: "vault.wechat-chatroom.list",
418
471
  method: "POST",
419
472
  path: "/application/open-vault/wechatgroupmsg/chatroomId",
@@ -421,7 +474,3 @@ export const ENDPOINTS = {
421
474
  description: "List WeChat group chatroom IDs",
422
475
  },
423
476
  };
424
- export const ENDPOINT_REGISTRY = Object.values(ENDPOINTS).reduce((accumulator, endpoint) => {
425
- accumulator[endpoint.key] = endpoint;
426
- return accumulator;
427
- }, {});
@@ -100,6 +100,70 @@ export function renderOutput(value, format) {
100
100
  return renderTable(rows);
101
101
  }
102
102
  }
103
+ /** Stream large jsonl/csv output row-by-row to avoid building a full string in memory. */
104
+ export async function streamOutputToFile(value, format, outputPath) {
105
+ if (format !== "jsonl" && format !== "csv")
106
+ return false;
107
+ const list = pickListForStreaming(value);
108
+ if (!list)
109
+ return false;
110
+ // Below this row count the join() approach is cheaper than per-row writes.
111
+ if (list.length < 1000)
112
+ return false;
113
+ const { dirname } = await import("node:path");
114
+ const { createWriteStream } = await import("node:fs");
115
+ await fs.mkdir(dirname(outputPath), { recursive: true });
116
+ const stream = createWriteStream(outputPath, { encoding: "utf8" });
117
+ try {
118
+ if (format === "jsonl") {
119
+ for (const item of list) {
120
+ await writeLine(stream, JSON.stringify(item));
121
+ }
122
+ }
123
+ else {
124
+ const objectRows = list.filter((row) => Boolean(row && typeof row === "object" && !Array.isArray(row)));
125
+ const columns = Array.from(new Set(objectRows.flatMap((row) => Object.keys(row))));
126
+ await writeLine(stream, columns.join(","));
127
+ for (const row of objectRows) {
128
+ const cells = columns.map((column) => csvEscape(formatScalar(row[column])));
129
+ await writeLine(stream, cells.join(","));
130
+ }
131
+ }
132
+ }
133
+ finally {
134
+ await new Promise((resolve, reject) => {
135
+ stream.end((err) => err ? reject(err) : resolve());
136
+ });
137
+ }
138
+ return true;
139
+ }
140
+ function pickListForStreaming(value) {
141
+ if (Array.isArray(value))
142
+ return value;
143
+ if (value && typeof value === "object") {
144
+ const list = value.list;
145
+ if (Array.isArray(list))
146
+ return list;
147
+ }
148
+ return null;
149
+ }
150
+ function csvEscape(value) {
151
+ let out = value;
152
+ if (/^[=+\-@\t\r]/.test(out))
153
+ out = "'" + out;
154
+ if (/[",\n]/.test(out))
155
+ return `"${out.replaceAll("\"", "\"\"")}"`;
156
+ return out;
157
+ }
158
+ function writeLine(stream, line) {
159
+ return new Promise((resolve, reject) => {
160
+ const ok = stream.write(line + "\n", (err) => err ? reject(err) : undefined);
161
+ if (ok)
162
+ resolve();
163
+ else
164
+ stream.once("drain", () => resolve());
165
+ });
166
+ }
103
167
  export async function saveOutputIfNeeded(content, outputPath) {
104
168
  if (!outputPath) {
105
169
  return;
@@ -1,5 +1,5 @@
1
1
  import { normalizeRows } from "./normalize.js";
2
- import { renderOutput, saveOutputIfNeeded } from "./output.js";
2
+ import { renderOutput, saveOutputIfNeeded, streamOutputToFile } from "./output.js";
3
3
  import { extractTitles, writeTitleCache } from "./titleCache.js";
4
4
  export async function printData(data, format, output, cache) {
5
5
  const normalized = normalizeRows(data);
@@ -21,11 +21,15 @@ export async function printData(data, format, output, cache) {
21
21
  process.stderr.write(`Total: ${meta.total}, showing: ${listLen}\n`);
22
22
  }
23
23
  }
24
- const content = renderOutput(normalized, format);
25
24
  if (output) {
25
+ if (await streamOutputToFile(normalized, format, output)) {
26
+ process.stdout.write(`${output}\n`);
27
+ return;
28
+ }
29
+ const content = renderOutput(normalized, format);
26
30
  await saveOutputIfNeeded(content, output);
27
31
  process.stdout.write(`${output}\n`);
28
32
  return;
29
33
  }
30
- process.stdout.write(`${content}\n`);
34
+ process.stdout.write(`${renderOutput(normalized, format)}\n`);
31
35
  }