@insightsentry/mcp 1.4.43 → 1.4.44

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 (50) hide show
  1. package/README.md +34 -1
  2. package/dist/api-client.d.ts +17 -5
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +125 -70
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/arg-coercion.js +10 -5
  7. package/dist/arg-coercion.js.map +1 -1
  8. package/dist/cancellation.d.ts +7 -0
  9. package/dist/cancellation.d.ts.map +1 -0
  10. package/dist/cancellation.js +48 -0
  11. package/dist/cancellation.js.map +1 -0
  12. package/dist/chart.d.ts +1 -1
  13. package/dist/chart.d.ts.map +1 -1
  14. package/dist/chart.js +96 -23
  15. package/dist/chart.js.map +1 -1
  16. package/dist/cli.d.ts +3 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +66 -15
  19. package/dist/cli.js.map +1 -1
  20. package/dist/csv.d.ts +14 -0
  21. package/dist/csv.d.ts.map +1 -0
  22. package/dist/csv.js +88 -0
  23. package/dist/csv.js.map +1 -0
  24. package/dist/history-merge.d.ts +2 -0
  25. package/dist/history-merge.d.ts.map +1 -0
  26. package/dist/history-merge.js +238 -0
  27. package/dist/history-merge.js.map +1 -0
  28. package/dist/history.d.ts +3 -1
  29. package/dist/history.d.ts.map +1 -1
  30. package/dist/history.js +59 -100
  31. package/dist/history.js.map +1 -1
  32. package/dist/index.js +12 -7
  33. package/dist/index.js.map +1 -1
  34. package/dist/response-storage.d.ts +7 -0
  35. package/dist/response-storage.d.ts.map +1 -1
  36. package/dist/response-storage.js +15 -2
  37. package/dist/response-storage.js.map +1 -1
  38. package/dist/tool-definitions.js +3 -3
  39. package/dist/tool-definitions.js.map +1 -1
  40. package/dist/tool-runner.d.ts +3 -1
  41. package/dist/tool-runner.d.ts.map +1 -1
  42. package/dist/tool-runner.js +23 -3
  43. package/dist/tool-runner.js.map +1 -1
  44. package/dist/version-status.d.ts +4 -2
  45. package/dist/version-status.d.ts.map +1 -1
  46. package/dist/version-status.js +83 -33
  47. package/dist/version-status.js.map +1 -1
  48. package/dist/websocket-stream.js +2 -2
  49. package/dist/websocket-stream.js.map +1 -1
  50. package/package.json +2 -2
@@ -0,0 +1,238 @@
1
+ import { createReadStream } from "node:fs";
2
+ import { mkdir, mkdtemp, open, rename, rm, unlink } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { createInterface } from "node:readline";
5
+ import { escapeCsvCell, readCsvRows } from "./csv.js";
6
+ const RUN_ROWS = 4096;
7
+ const RUN_BYTES = 1024 * 1024;
8
+ const MERGE_FAN_IN = 8;
9
+ class BufferedWriter {
10
+ file;
11
+ parts = [];
12
+ bytes = 0;
13
+ constructor(file) {
14
+ this.file = file;
15
+ }
16
+ async write(value) {
17
+ this.parts.push(value);
18
+ this.bytes += Buffer.byteLength(value);
19
+ if (this.bytes >= 64 * 1024)
20
+ await this.flush();
21
+ }
22
+ async flush() {
23
+ if (!this.parts.length)
24
+ return;
25
+ await this.file.writeFile(this.parts.join(""), "utf8");
26
+ this.parts = [];
27
+ this.bytes = 0;
28
+ }
29
+ }
30
+ async function writeRows(file, rows, signal) {
31
+ const handle = await open(file, "wx");
32
+ try {
33
+ const writer = new BufferedWriter(handle);
34
+ for await (const row of rows) {
35
+ signal?.throwIfAborted();
36
+ await writer.write(`${JSON.stringify(row)}\n`);
37
+ }
38
+ await writer.flush();
39
+ }
40
+ finally {
41
+ await handle.close();
42
+ }
43
+ }
44
+ async function* readRun(file, signal) {
45
+ const stream = createReadStream(file, { encoding: "utf8", highWaterMark: 16 * 1024, signal });
46
+ const lines = createInterface({ input: stream, crlfDelay: Infinity });
47
+ try {
48
+ for await (const line of lines) {
49
+ signal?.throwIfAborted();
50
+ yield JSON.parse(line);
51
+ }
52
+ }
53
+ finally {
54
+ lines.close();
55
+ stream.destroy();
56
+ }
57
+ }
58
+ async function* mergeRuns(files, compare, signal) {
59
+ const readers = files.map((file) => readRun(file, signal));
60
+ const heap = [];
61
+ const push = (entry) => {
62
+ let index = heap.length;
63
+ heap.push(entry);
64
+ while (index > 0) {
65
+ const parent = (index - 1) >>> 1;
66
+ if (compare(heap[parent].row, entry.row) <= 0)
67
+ break;
68
+ heap[index] = heap[parent];
69
+ index = parent;
70
+ }
71
+ heap[index] = entry;
72
+ };
73
+ const pop = () => {
74
+ const first = heap[0];
75
+ const last = heap.pop();
76
+ if (heap.length && last) {
77
+ let index = 0;
78
+ while (index * 2 + 1 < heap.length) {
79
+ let child = index * 2 + 1;
80
+ if (child + 1 < heap.length && compare(heap[child + 1].row, heap[child].row) < 0)
81
+ child++;
82
+ if (compare(last.row, heap[child].row) <= 0)
83
+ break;
84
+ heap[index] = heap[child];
85
+ index = child;
86
+ }
87
+ heap[index] = last;
88
+ }
89
+ return first;
90
+ };
91
+ try {
92
+ for (let index = 0; index < readers.length; index++) {
93
+ const item = await readers[index].next();
94
+ if (!item.done)
95
+ push({ row: item.value, reader: index });
96
+ }
97
+ while (heap.length) {
98
+ signal?.throwIfAborted();
99
+ const item = pop();
100
+ yield item.row;
101
+ const next = await readers[item.reader].next();
102
+ if (!next.done)
103
+ push({ row: next.value, reader: item.reader });
104
+ }
105
+ }
106
+ finally {
107
+ await Promise.all(readers.map((reader) => reader.return(undefined)));
108
+ }
109
+ }
110
+ async function* externalSort(source, compare, directory, prefix, signal) {
111
+ let rows = [];
112
+ let bytes = 0;
113
+ let sequence = 0;
114
+ let files = [];
115
+ const nextFile = () => path.join(directory, `${prefix}-${sequence++}.ndjson`);
116
+ const spill = async () => {
117
+ if (!rows.length)
118
+ return;
119
+ rows.sort(compare);
120
+ const file = nextFile();
121
+ await writeRows(file, rows, signal);
122
+ files.push(file);
123
+ rows = [];
124
+ bytes = 0;
125
+ };
126
+ for await (const row of source) {
127
+ rows.push(row);
128
+ bytes += Buffer.byteLength(JSON.stringify(row));
129
+ if (rows.length >= RUN_ROWS || bytes >= RUN_BYTES)
130
+ await spill();
131
+ }
132
+ await spill();
133
+ while (files.length > MERGE_FAN_IN) {
134
+ const next = [];
135
+ for (let index = 0; index < files.length; index += MERGE_FAN_IN) {
136
+ const group = files.slice(index, index + MERGE_FAN_IN);
137
+ if (group.length === 1) {
138
+ next.push(group[0]);
139
+ continue;
140
+ }
141
+ const file = nextFile();
142
+ await writeRows(file, mergeRuns(group, compare, signal), signal);
143
+ for (const old of group)
144
+ await unlink(old);
145
+ next.push(file);
146
+ }
147
+ files = next;
148
+ }
149
+ yield* mergeRuns(files, compare, signal);
150
+ for (const file of files)
151
+ await unlink(file);
152
+ }
153
+ export async function mergeCsvFiles(files, outputPath, signal) {
154
+ signal?.throwIfAborted();
155
+ await mkdir(path.dirname(outputPath), { recursive: true });
156
+ const workspace = await mkdtemp(path.join(path.dirname(outputPath), ".insight-merge-"));
157
+ const headers = [];
158
+ const positions = new Map();
159
+ let ordinal = 0;
160
+ async function* inputRows() {
161
+ for (const file of files) {
162
+ let columns;
163
+ let mapping = [];
164
+ let keyIndices = [];
165
+ for await (const values of readCsvRows(file, signal)) {
166
+ if (!columns) {
167
+ columns = values;
168
+ keyIndices = ["code", "bar_type", "time"].map((key) => values.indexOf(key));
169
+ if (keyIndices.includes(-1))
170
+ throw new Error("Merged CSV requires code, bar_type, and time columns for deduplication");
171
+ mapping = columns.map((column) => {
172
+ let index = positions.get(column);
173
+ if (index === undefined) {
174
+ index = headers.length;
175
+ positions.set(column, index);
176
+ headers.push(column);
177
+ }
178
+ return index;
179
+ });
180
+ continue;
181
+ }
182
+ const projected = [];
183
+ for (let index = 0; index < mapping.length; index++)
184
+ projected[mapping[index]] = values[index] ?? "";
185
+ yield {
186
+ key: JSON.stringify(keyIndices.map((index) => values[index] ?? "")),
187
+ first: ordinal,
188
+ last: ordinal++,
189
+ values: projected,
190
+ };
191
+ }
192
+ }
193
+ }
194
+ const byKey = (a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : a.last - b.last);
195
+ async function* deduplicated() {
196
+ let pending;
197
+ for await (const row of externalSort(inputRows(), byKey, workspace, "key", signal)) {
198
+ if (pending?.key === row.key) {
199
+ pending.last = row.last;
200
+ pending.values = row.values;
201
+ }
202
+ else {
203
+ if (pending)
204
+ yield pending;
205
+ pending = row;
206
+ }
207
+ }
208
+ if (pending)
209
+ yield pending;
210
+ }
211
+ try {
212
+ const output = path.join(workspace, "merged.csv");
213
+ const handle = await open(output, "wx");
214
+ try {
215
+ const writer = new BufferedWriter(handle);
216
+ let wroteHeader = false;
217
+ for await (const row of externalSort(deduplicated(), (a, b) => a.first - b.first, workspace, "order", signal)) {
218
+ if (!wroteHeader) {
219
+ await writer.write(`${headers.map(escapeCsvCell).join(",")}\n`);
220
+ wroteHeader = true;
221
+ }
222
+ await writer.write(`${headers.map((_header, index) => escapeCsvCell(row.values[index])).join(",")}\n`);
223
+ }
224
+ if (!wroteHeader && headers.length)
225
+ await writer.write(`${headers.map(escapeCsvCell).join(",")}\n`);
226
+ await writer.flush();
227
+ }
228
+ finally {
229
+ await handle.close();
230
+ }
231
+ signal?.throwIfAborted();
232
+ await rename(output, outputPath);
233
+ }
234
+ finally {
235
+ await rm(workspace, { recursive: true, force: true });
236
+ }
237
+ }
238
+ //# sourceMappingURL=history-merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-merge.js","sourceRoot":"","sources":["../src/history-merge.ts"],"names":[],"mappings":"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"}
package/dist/history.d.ts CHANGED
@@ -1,6 +1,7 @@
1
+ import { type ApiRequestOptions } from "./api-client.js";
1
2
  export type HistoryBarType = "second" | "minute" | "hour" | "day" | "week" | "month";
2
3
  export type HistoryOutputFormat = "json" | "csv" | "both";
3
- export type RequestFn = (method: string, pathTemplate: string, params: Record<string, any>) => Promise<any>;
4
+ export type RequestFn = (method: string, pathTemplate: string, params: Record<string, any>, options?: ApiRequestOptions) => Promise<any>;
4
5
  export interface DownloadHistoryOptions {
5
6
  symbol: string;
6
7
  from: string;
@@ -30,6 +31,7 @@ export interface HistoryProgressEvent {
30
31
  error?: string;
31
32
  }
32
33
  export interface DownloadHistoryDeps {
34
+ signal?: AbortSignal;
33
35
  request: RequestFn;
34
36
  onProgress?: (event: HistoryProgressEvent) => void;
35
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AACrF,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAC1D,MAAM,MAAM,SAAS,GAAG,CACtB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KACxB,OAAO,CAAC,GAAG,CAAC,CAAC;AAElB,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,cAAc,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,SAAS,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IAC5B,QAAQ,EAAE,qBAAqB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxE;AAsBD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,sBAAsB,EAC/B,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC,GACzC,OAAO,CAAC,WAAW,CAAC,CAStB;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,EAC/B,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,qBAAqB,CAAC,CA+KhC;AAED,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB9E;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,GAAG,GAAG,MAAM,CAyBnD"}
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"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"}
package/dist/history.js CHANGED
@@ -2,6 +2,8 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import { mkdir, readdir, readFile, rename, rmdir, unlink, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { isTerminalApiError } from "./api-client.js";
5
+ import { CsvParser, escapeCsvCell } from "./csv.js";
6
+ import { mergeCsvFiles } from "./history-merge.js";
5
7
  import { validateHistoryBarInterval } from "./history-validation.js";
6
8
  const HISTORY_PATH = "/v3/symbols/{symbol}/history";
7
9
  const SERIES_PATH = "/v3/symbols/{symbol}/series";
@@ -24,11 +26,26 @@ export async function planHistoryRequests(options, deps) {
24
26
  };
25
27
  }
26
28
  export async function downloadHistory(options, deps) {
29
+ deps.signal?.throwIfAborted();
30
+ const controller = new AbortController();
31
+ const abort = () => controller.abort(deps.signal?.reason);
32
+ deps.signal?.addEventListener("abort", abort, { once: true });
33
+ try {
34
+ return await downloadHistoryRun(options, { ...deps, signal: controller.signal }, controller);
35
+ }
36
+ finally {
37
+ deps.signal?.removeEventListener("abort", abort);
38
+ }
39
+ }
40
+ async function downloadHistoryRun(options, deps, controller) {
27
41
  const format = options.format ?? "csv";
28
42
  const concurrency = normalizeConcurrency(options.concurrency);
29
43
  const shouldMergeCsv = options.merge ?? true;
30
44
  await validateOutputDirectory(options.output_dir);
31
- const plan = await planHistoryRequests(options, deps);
45
+ const plan = await planHistoryRequests(options, {
46
+ request: (method, route, params) => deps.request(method, route, params, { signal: deps.signal }),
47
+ });
48
+ deps.signal?.throwIfAborted();
32
49
  const mergeCsvFilesByRequestIndex = new Map();
33
50
  const createdCsvFiles = new Set();
34
51
  const result = {
@@ -45,7 +62,7 @@ export async function downloadHistory(options, deps) {
45
62
  let nextIndex = 0;
46
63
  let terminalError = null;
47
64
  async function worker() {
48
- while (!terminalError && nextIndex < plan.requests.length) {
65
+ while (!controller.signal.aborted && nextIndex < plan.requests.length) {
49
66
  const requestIndex = nextIndex;
50
67
  const request = plan.requests[requestIndex];
51
68
  nextIndex += 1;
@@ -53,6 +70,7 @@ export async function downloadHistory(options, deps) {
53
70
  const startDate = String(request.params.start_date ?? `${options.from}_${options.to}`);
54
71
  const symbol = String(request.params.symbol);
55
72
  try {
73
+ controller.signal.throwIfAborted();
56
74
  const existingTargetFiles = !options.overwrite
57
75
  ? await findExistingTargetFiles(request.outputBasePath, format, request)
58
76
  : null;
@@ -70,7 +88,8 @@ export async function downloadHistory(options, deps) {
70
88
  });
71
89
  continue;
72
90
  }
73
- const response = filterResponseForRequest(await deps.request("GET", request.method === "series" ? SERIES_PATH : HISTORY_PATH, request.params), options, request);
91
+ const response = filterResponseForRequest(await deps.request("GET", request.method === "series" ? SERIES_PATH : HISTORY_PATH, request.params, { signal: controller.signal }), options, request);
92
+ controller.signal.throwIfAborted();
74
93
  const message = response?.message ?? response?.error;
75
94
  if (message && !Array.isArray(response?.series)) {
76
95
  result.skipped += 1;
@@ -103,6 +122,7 @@ export async function downloadHistory(options, deps) {
103
122
  continue;
104
123
  }
105
124
  for (const filePath of targetFiles) {
125
+ controller.signal.throwIfAborted();
106
126
  await removeOutputMarker(filePath);
107
127
  let content;
108
128
  if (filePath.endsWith(".csv")) {
@@ -133,8 +153,11 @@ export async function downloadHistory(options, deps) {
133
153
  });
134
154
  }
135
155
  catch (error) {
156
+ if (controller.signal.aborted)
157
+ return;
136
158
  if (isTerminalApiError(error)) {
137
159
  terminalError = error;
160
+ controller.abort(error);
138
161
  nextIndex = plan.requests.length;
139
162
  throw error;
140
163
  }
@@ -154,19 +177,26 @@ export async function downloadHistory(options, deps) {
154
177
  }
155
178
  }
156
179
  }
157
- await Promise.all(Array.from({ length: Math.min(concurrency, plan.requests.length) }, () => worker()));
158
- if (shouldMergeCsv && (format === "csv" || format === "both")) {
180
+ const workers = await Promise.allSettled(Array.from({ length: Math.min(concurrency, plan.requests.length) }, () => worker()));
181
+ if (terminalError)
182
+ throw terminalError;
183
+ for (const worker of workers)
184
+ if (worker.status === "rejected")
185
+ throw worker.reason;
186
+ controller.signal.throwIfAborted();
187
+ if (result.failed === 0 && shouldMergeCsv && (format === "csv" || format === "both")) {
159
188
  const orderedCsvFiles = plan.requests.flatMap((_request, index) => mergeCsvFilesByRequestIndex.get(index) ?? []);
160
189
  if (orderedCsvFiles.length > 0) {
161
190
  const mergedFile = mergedCsvPath(options, path.basename(path.dirname(orderedCsvFiles[0])));
162
- await mergeCsvFiles(orderedCsvFiles, mergedFile);
191
+ await mergeCsvFiles(orderedCsvFiles, mergedFile, controller.signal);
163
192
  await removeLegacyMergedMarker(mergedFile);
164
193
  result.merged_file = mergedFile;
165
194
  result.files.push(mergedFile);
166
195
  if (!options.keep_chunks) {
167
196
  const removableCsvFiles = await filterRemovableChunkFiles(orderedCsvFiles, createdCsvFiles);
168
197
  await removeChunkFilesAndMarkers(removableCsvFiles, options.output_dir);
169
- result.files = result.files.filter((filePath) => !removableCsvFiles.includes(filePath));
198
+ const removedFiles = new Set(removableCsvFiles);
199
+ result.files = result.files.filter((filePath) => !removedFiles.has(filePath));
170
200
  }
171
201
  }
172
202
  }
@@ -251,6 +281,8 @@ function planRequestsForSymbol(options, symbol, outputSymbol = symbol) {
251
281
  async function planFuturesHistoryRequests(options, request) {
252
282
  const schedule = await fetchContractSchedule(options.symbol, request);
253
283
  const range = monthRangeFor(options.from, options.to);
284
+ const firstDay = formatDay(parseDay(options.from, "start"));
285
+ const lastDay = formatDay(parseDay(options.to, "end"));
254
286
  const lookback = options.contract_lookback_months ?? DEFAULT_CONTRACT_LOOKBACK_MONTHS;
255
287
  const requests = [];
256
288
  for (let year = range.start.year; year <= range.end.year + 1; year += 1) {
@@ -267,6 +299,8 @@ async function planFuturesHistoryRequests(options, request) {
267
299
  const symbol = `${schedule.fullBaseCode}${monthCode}${year}`;
268
300
  const startDate = formatMonth(month);
269
301
  for (const archiveDate of expandArchiveStartDates(startDate, options.bar_type)) {
302
+ if (options.bar_type === "second" && (archiveDate < firstDay || archiveDate > lastDay))
303
+ continue;
270
304
  requests.push({
271
305
  method: "history",
272
306
  params: {
@@ -336,6 +370,9 @@ function validateOptions(options) {
336
370
  if (![...HISTORY_BAR_TYPES, ...SERIES_BAR_TYPES].includes(options.bar_type)) {
337
371
  throw new Error("bar_type must be second, minute, hour, day, week, or month");
338
372
  }
373
+ if (compareDays(parseDay(options.from, "start"), parseDay(options.to, "end")) > 0) {
374
+ throw new Error("from must be before or equal to to");
375
+ }
339
376
  normalizeConcurrency(options.concurrency);
340
377
  if (options.format && !["json", "csv", "both"].includes(options.format)) {
341
378
  throw new Error("format must be json, csv, or both");
@@ -508,47 +545,6 @@ function outputBasePathForResponse(request, response) {
508
545
  function mergedCsvPath(options, barTypeLabel) {
509
546
  return path.join(path.resolve(options.output_dir), sanitizePathPart(options.symbol), sanitizePathPart(barTypeLabel), "merged.csv");
510
547
  }
511
- async function mergeCsvFiles(files, outputPath) {
512
- const headers = [];
513
- const headerSet = new Set();
514
- const rowsByKey = new Map();
515
- for (const file of files) {
516
- const content = await readFile(file, "utf8");
517
- if (!content.trim())
518
- continue;
519
- const lines = content.split(/\r?\n/).filter((line) => line.length > 0);
520
- if (lines.length === 0)
521
- continue;
522
- const columns = parseCsvLine(lines[0]);
523
- const codeIndex = columns.indexOf("code");
524
- const barTypeIndex = columns.indexOf("bar_type");
525
- const timeIndex = columns.indexOf("time");
526
- if (codeIndex === -1 || barTypeIndex === -1 || timeIndex === -1) {
527
- throw new Error("Merged CSV requires code, bar_type, and time columns for deduplication");
528
- }
529
- for (const column of columns) {
530
- if (!headerSet.has(column)) {
531
- headerSet.add(column);
532
- headers.push(column);
533
- }
534
- }
535
- for (const line of lines.slice(1)) {
536
- const values = parseCsvLine(line);
537
- const key = [codeIndex, barTypeIndex, timeIndex]
538
- .map((index) => values[index] ?? "")
539
- .join("\u0000");
540
- const row = {};
541
- for (let index = 0; index < columns.length; index += 1) {
542
- row[columns[index]] = values[index] ?? "";
543
- }
544
- rowsByKey.set(key, row);
545
- }
546
- }
547
- const rows = Array.from(rowsByKey.values()).map((row) => headers.map((header) => row[header] ?? ""));
548
- const content = headers.length > 0 ? serializeCsvRows(headers, rows) : "";
549
- await writeFileAtomic(outputPath, content);
550
- return content;
551
- }
552
548
  async function allFilesAreResumable(files, request) {
553
549
  for (const file of files) {
554
550
  if (!(await isExistingOutputFileResumable(file, request))) {
@@ -576,21 +572,22 @@ async function isExistingOutputFileResumable(file, request) {
576
572
  }
577
573
  }
578
574
  function validateCsvContent(content) {
579
- if (!content.trim())
580
- throw new Error("CSV output is empty");
581
- const lines = content.split(/\r?\n/).filter((line) => line.length > 0);
582
- if (lines.length < 2)
583
- throw new Error("CSV output has no data rows");
584
- const columns = parseCsvLine(lines[0]);
585
- const required = ["code", "bar_type", "time"];
586
- for (const column of required) {
587
- if (!columns.includes(column)) {
588
- throw new Error(`CSV output is missing ${column} column`);
575
+ const parser = new CsvParser();
576
+ let rows = 0;
577
+ const validate = (row) => {
578
+ if (rows++ === 0) {
579
+ for (const column of ["code", "bar_type", "time"]) {
580
+ if (!row.includes(column))
581
+ throw new Error(`CSV output is missing ${column} column`);
582
+ }
589
583
  }
590
- }
591
- for (const line of lines.slice(1)) {
592
- parseCsvLine(line);
593
- }
584
+ };
585
+ for (const row of parser.push(content))
586
+ validate(row);
587
+ for (const row of parser.finish())
588
+ validate(row);
589
+ if (rows < 2)
590
+ throw new Error("CSV output has no data rows");
594
591
  }
595
592
  function validateJsonContent(content) {
596
593
  if (!content.trim())
@@ -772,44 +769,6 @@ function serializeCsvRows(headers, rows) {
772
769
  }
773
770
  return `${lines.join("\n")}\n`;
774
771
  }
775
- function escapeCsvCell(value) {
776
- if (value === undefined || value === null)
777
- return "";
778
- const text = typeof value === "object" ? JSON.stringify(value) : String(value);
779
- if (/[",\n\r]/.test(text)) {
780
- return `"${text.replace(/"/g, '""')}"`;
781
- }
782
- return text;
783
- }
784
- function parseCsvLine(line) {
785
- const cells = [];
786
- let current = "";
787
- let inQuotes = false;
788
- for (let index = 0; index < line.length; index += 1) {
789
- const char = line[index];
790
- if (char === '"') {
791
- if (inQuotes && line[index + 1] === '"') {
792
- current += '"';
793
- index += 1;
794
- }
795
- else {
796
- inQuotes = !inQuotes;
797
- }
798
- continue;
799
- }
800
- if (char === "," && !inQuotes) {
801
- cells.push(current);
802
- current = "";
803
- continue;
804
- }
805
- current += char;
806
- }
807
- if (inQuotes) {
808
- throw new Error("CSV line has an unterminated quoted field");
809
- }
810
- cells.push(current);
811
- return cells;
812
- }
813
772
  function parseSettlementMonth(value) {
814
773
  const match = /^\d{4}(?<month>\d{2})\d{2}$/.exec(value);
815
774
  if (!match?.groups)