@tradejs/infra 1.0.8 → 1.0.10

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/dist/ai.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AiDatasetRow } from '@tradejs/types';
2
- export { m as mergeJsonlFiles, t as toFileToken } from './mlDatasetFile-sRGWgR_o.mjs';
2
+ export { m as mergeJsonlFiles, t as toFileToken } from './mlDatasetFile-Czx__g9M.mjs';
3
3
 
4
4
  declare const getAiChunkFilePath: (strategyName: string, chunkId: string, outDir?: string) => string;
5
5
  declare const appendAiDatasetRow: (params: {
@@ -14,6 +14,11 @@ declare const closeAllAiDatasetWriters: () => Promise<void>;
14
14
  declare const listAiChunkFiles: (params: {
15
15
  strategyName: string;
16
16
  outDir?: string;
17
+ runId?: string;
18
+ }) => Promise<string[]>;
19
+ declare const listAiChunkRunIds: (params: {
20
+ strategyName: string;
21
+ outDir?: string;
17
22
  }) => Promise<string[]>;
18
23
  declare const listAiChunkStrategies: (params?: {
19
24
  outDir?: string;
@@ -23,14 +28,45 @@ declare const mergeAiJsonlFiles: (params: {
23
28
  outPath: string;
24
29
  maxRowsInMemory?: number;
25
30
  maxBytesInMemory?: number;
31
+ maxOpenRuns?: number;
32
+ shouldIncludeRow?: (row: AiDatasetRow) => boolean;
26
33
  }) => Promise<void>;
27
- declare const readAiDatasetRows: (params: {
34
+ declare const splitAiMergedDatasetFile: (params: {
28
35
  filePath: string;
36
+ monthsPerPart?: number;
37
+ }) => Promise<{
38
+ partPaths: string[];
39
+ partCount: number;
40
+ rowCount: number;
41
+ splitApplied: boolean;
42
+ }>;
43
+ declare const readAiDatasetRows: (params: {
44
+ filePath?: string;
45
+ filePaths?: string[];
29
46
  limitFromEnd?: number;
30
47
  skipFromEnd?: number;
31
48
  }) => Promise<{
32
49
  rows: AiDatasetRow[];
33
50
  totalRows: number;
34
51
  }>;
52
+ declare const countAiDatasetRows: (params: {
53
+ filePath?: string;
54
+ filePaths?: string[];
55
+ limitFromEnd?: number;
56
+ skipFromEnd?: number;
57
+ }) => Promise<{
58
+ totalRows: number;
59
+ selectedRows: number;
60
+ }>;
61
+ declare const streamAiDatasetRows: (params: {
62
+ filePath?: string;
63
+ filePaths?: string[];
64
+ limitFromEnd?: number;
65
+ skipFromEnd?: number;
66
+ onRow: (row: AiDatasetRow, index: number) => Promise<void> | void;
67
+ }) => Promise<{
68
+ totalRows: number;
69
+ selectedRows: number;
70
+ }>;
35
71
 
36
- export { appendAiDatasetRow, closeAiDatasetWriter, closeAllAiDatasetWriters, flushAiDatasetWriter, getAiChunkFilePath, listAiChunkFiles, listAiChunkStrategies, mergeAiJsonlFiles, readAiDatasetRows };
72
+ export { appendAiDatasetRow, closeAiDatasetWriter, closeAllAiDatasetWriters, countAiDatasetRows, flushAiDatasetWriter, getAiChunkFilePath, listAiChunkFiles, listAiChunkRunIds, listAiChunkStrategies, mergeAiJsonlFiles, readAiDatasetRows, splitAiMergedDatasetFile, streamAiDatasetRows };
package/dist/ai.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AiDatasetRow } from '@tradejs/types';
2
- export { m as mergeJsonlFiles, t as toFileToken } from './mlDatasetFile-sRGWgR_o.js';
2
+ export { m as mergeJsonlFiles, t as toFileToken } from './mlDatasetFile-Czx__g9M.js';
3
3
 
4
4
  declare const getAiChunkFilePath: (strategyName: string, chunkId: string, outDir?: string) => string;
5
5
  declare const appendAiDatasetRow: (params: {
@@ -14,6 +14,11 @@ declare const closeAllAiDatasetWriters: () => Promise<void>;
14
14
  declare const listAiChunkFiles: (params: {
15
15
  strategyName: string;
16
16
  outDir?: string;
17
+ runId?: string;
18
+ }) => Promise<string[]>;
19
+ declare const listAiChunkRunIds: (params: {
20
+ strategyName: string;
21
+ outDir?: string;
17
22
  }) => Promise<string[]>;
18
23
  declare const listAiChunkStrategies: (params?: {
19
24
  outDir?: string;
@@ -23,14 +28,45 @@ declare const mergeAiJsonlFiles: (params: {
23
28
  outPath: string;
24
29
  maxRowsInMemory?: number;
25
30
  maxBytesInMemory?: number;
31
+ maxOpenRuns?: number;
32
+ shouldIncludeRow?: (row: AiDatasetRow) => boolean;
26
33
  }) => Promise<void>;
27
- declare const readAiDatasetRows: (params: {
34
+ declare const splitAiMergedDatasetFile: (params: {
28
35
  filePath: string;
36
+ monthsPerPart?: number;
37
+ }) => Promise<{
38
+ partPaths: string[];
39
+ partCount: number;
40
+ rowCount: number;
41
+ splitApplied: boolean;
42
+ }>;
43
+ declare const readAiDatasetRows: (params: {
44
+ filePath?: string;
45
+ filePaths?: string[];
29
46
  limitFromEnd?: number;
30
47
  skipFromEnd?: number;
31
48
  }) => Promise<{
32
49
  rows: AiDatasetRow[];
33
50
  totalRows: number;
34
51
  }>;
52
+ declare const countAiDatasetRows: (params: {
53
+ filePath?: string;
54
+ filePaths?: string[];
55
+ limitFromEnd?: number;
56
+ skipFromEnd?: number;
57
+ }) => Promise<{
58
+ totalRows: number;
59
+ selectedRows: number;
60
+ }>;
61
+ declare const streamAiDatasetRows: (params: {
62
+ filePath?: string;
63
+ filePaths?: string[];
64
+ limitFromEnd?: number;
65
+ skipFromEnd?: number;
66
+ onRow: (row: AiDatasetRow, index: number) => Promise<void> | void;
67
+ }) => Promise<{
68
+ totalRows: number;
69
+ selectedRows: number;
70
+ }>;
35
71
 
36
- export { appendAiDatasetRow, closeAiDatasetWriter, closeAllAiDatasetWriters, flushAiDatasetWriter, getAiChunkFilePath, listAiChunkFiles, listAiChunkStrategies, mergeAiJsonlFiles, readAiDatasetRows };
72
+ export { appendAiDatasetRow, closeAiDatasetWriter, closeAllAiDatasetWriters, countAiDatasetRows, flushAiDatasetWriter, getAiChunkFilePath, listAiChunkFiles, listAiChunkRunIds, listAiChunkStrategies, mergeAiJsonlFiles, readAiDatasetRows, splitAiMergedDatasetFile, streamAiDatasetRows };
package/dist/ai.js CHANGED
@@ -33,13 +33,17 @@ __export(ai_exports, {
33
33
  appendAiDatasetRow: () => appendAiDatasetRow,
34
34
  closeAiDatasetWriter: () => closeAiDatasetWriter,
35
35
  closeAllAiDatasetWriters: () => closeAllAiDatasetWriters,
36
+ countAiDatasetRows: () => countAiDatasetRows,
36
37
  flushAiDatasetWriter: () => flushAiDatasetWriter,
37
38
  getAiChunkFilePath: () => getAiChunkFilePath,
38
39
  listAiChunkFiles: () => listAiChunkFiles,
40
+ listAiChunkRunIds: () => listAiChunkRunIds,
39
41
  listAiChunkStrategies: () => listAiChunkStrategies,
40
42
  mergeAiJsonlFiles: () => mergeAiJsonlFiles,
41
43
  mergeJsonlFiles: () => mergeJsonlFiles,
42
44
  readAiDatasetRows: () => readAiDatasetRows,
45
+ splitAiMergedDatasetFile: () => splitAiMergedDatasetFile,
46
+ streamAiDatasetRows: () => streamAiDatasetRows,
43
47
  toFileToken: () => toFileToken
44
48
  });
45
49
  module.exports = __toCommonJS(ai_exports);
@@ -50,21 +54,99 @@ var import_fs2 = require("fs");
50
54
  var import_promises2 = __toESM(require("fs/promises"));
51
55
  var import_path2 = __toESM(require("path"));
52
56
  var import_node_fs = require("fs");
53
- var import_node_readline = __toESM(require("readline"));
57
+ var import_node_readline2 = __toESM(require("readline"));
58
+
59
+ // ../../node_modules/date-fns/toDate.mjs
60
+ function toDate(argument) {
61
+ const argStr = Object.prototype.toString.call(argument);
62
+ if (argument instanceof Date || typeof argument === "object" && argStr === "[object Date]") {
63
+ return new argument.constructor(+argument);
64
+ } else if (typeof argument === "number" || argStr === "[object Number]" || typeof argument === "string" || argStr === "[object String]") {
65
+ return new Date(argument);
66
+ } else {
67
+ return /* @__PURE__ */ new Date(NaN);
68
+ }
69
+ }
70
+
71
+ // ../../node_modules/date-fns/constructFrom.mjs
72
+ function constructFrom(date, value) {
73
+ if (date instanceof Date) {
74
+ return new date.constructor(value);
75
+ } else {
76
+ return new Date(value);
77
+ }
78
+ }
79
+
80
+ // ../../node_modules/date-fns/addMonths.mjs
81
+ function addMonths(date, amount) {
82
+ const _date = toDate(date);
83
+ if (isNaN(amount)) return constructFrom(date, NaN);
84
+ if (!amount) {
85
+ return _date;
86
+ }
87
+ const dayOfMonth = _date.getDate();
88
+ const endOfDesiredMonth = constructFrom(date, _date.getTime());
89
+ endOfDesiredMonth.setMonth(_date.getMonth() + amount + 1, 0);
90
+ const daysInMonth = endOfDesiredMonth.getDate();
91
+ if (dayOfMonth >= daysInMonth) {
92
+ return endOfDesiredMonth;
93
+ } else {
94
+ _date.setFullYear(
95
+ endOfDesiredMonth.getFullYear(),
96
+ endOfDesiredMonth.getMonth(),
97
+ dayOfMonth
98
+ );
99
+ return _date;
100
+ }
101
+ }
102
+
103
+ // ../../node_modules/date-fns/startOfMonth.mjs
104
+ function startOfMonth(date) {
105
+ const _date = toDate(date);
106
+ _date.setDate(1);
107
+ _date.setHours(0, 0, 0, 0);
108
+ return _date;
109
+ }
54
110
 
55
111
  // src/mlDatasetFile.ts
56
112
  var import_events = require("events");
57
113
  var import_fs = require("fs");
58
114
  var import_promises = __toESM(require("fs/promises"));
59
115
  var import_path = __toESM(require("path"));
116
+ var import_node_readline = __toESM(require("readline"));
60
117
  var toFileToken = (value) => value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "") || "any";
61
118
  var mergeJsonlFiles = async (params) => {
62
- const { filePaths, outPath } = params;
119
+ const { filePaths, outPath, shouldIncludeRow } = params;
63
120
  await import_promises.default.mkdir(import_path.default.dirname(outPath), { recursive: true });
64
121
  const stream = (0, import_fs.createWriteStream)(outPath, { encoding: "utf8" });
65
122
  const done = Promise.all([(0, import_events.once)(stream, "finish"), (0, import_events.once)(stream, "close")]);
66
123
  try {
67
124
  for (const filePath of filePaths) {
125
+ if (shouldIncludeRow) {
126
+ const reader2 = import_node_readline.default.createInterface({
127
+ input: (0, import_fs.createReadStream)(filePath, { encoding: "utf8" }),
128
+ crlfDelay: Infinity
129
+ });
130
+ try {
131
+ for await (const line of reader2) {
132
+ const trimmed = line.trim();
133
+ if (!trimmed) {
134
+ continue;
135
+ }
136
+ const row = JSON.parse(trimmed);
137
+ if (!shouldIncludeRow(row)) {
138
+ continue;
139
+ }
140
+ if (!stream.write(`${trimmed}
141
+ `)) {
142
+ await (0, import_events.once)(stream, "drain");
143
+ }
144
+ }
145
+ } finally {
146
+ reader2.close();
147
+ }
148
+ continue;
149
+ }
68
150
  const reader = (0, import_fs.createReadStream)(filePath, { encoding: "utf8" });
69
151
  for await (const chunk of reader) {
70
152
  if (!stream.write(chunk)) {
@@ -83,12 +165,23 @@ var DEFAULT_DIR = "data/ai/export";
83
165
  var AI_DATASET_WRITE_BATCH_SIZE = 100;
84
166
  var AI_MERGE_SORT_RUN_MAX_ROWS = 2e3;
85
167
  var AI_MERGE_SORT_RUN_MAX_BYTES = 16 * 1024 * 1024;
168
+ var AI_MERGE_SORT_MAX_OPEN_RUNS = 16;
86
169
  var AI_CHUNK_FILE_RE = /^ai-dataset-(.+)-chunk-[^.]+\.jsonl$/;
170
+ var BACKTEST_RUN_CHUNK_ID_RE = /^(\d{12}-[a-f0-9]{8})-/;
87
171
  var writerByPath = /* @__PURE__ */ new Map();
88
172
  var getAiChunkFilePath = (strategyName, chunkId, outDir = DEFAULT_DIR) => import_path2.default.join(
89
173
  outDir,
90
174
  `ai-dataset-${toFileToken(strategyName)}-chunk-${toFileToken(chunkId)}.jsonl`
91
175
  );
176
+ var getAiChunkFilePrefix = (strategyName, runId) => `ai-dataset-${toFileToken(strategyName)}-chunk-${runId ? `${toFileToken(runId)}-` : ""}`;
177
+ var getRunIdFromAiChunkFileName = (strategyName, fileName) => {
178
+ const prefix = getAiChunkFilePrefix(strategyName);
179
+ if (!fileName.startsWith(prefix) || !fileName.endsWith(".jsonl")) {
180
+ return "";
181
+ }
182
+ const chunkToken = fileName.slice(prefix.length, -".jsonl".length);
183
+ return chunkToken.match(BACKTEST_RUN_CHUNK_ID_RE)?.[1] ?? "";
184
+ };
92
185
  var appendAiDatasetRow = async (params) => {
93
186
  const { strategyName, chunkId, row, outDir = DEFAULT_DIR } = params;
94
187
  const filePath = getAiChunkFilePath(strategyName, chunkId, outDir);
@@ -164,8 +257,8 @@ var closeAllAiDatasetWriters = async () => {
164
257
  }
165
258
  };
166
259
  var listAiChunkFiles = async (params) => {
167
- const { strategyName, outDir = DEFAULT_DIR } = params;
168
- const prefix = `ai-dataset-${toFileToken(strategyName)}-chunk-`;
260
+ const { strategyName, outDir = DEFAULT_DIR, runId } = params;
261
+ const prefix = getAiChunkFilePrefix(strategyName, runId);
169
262
  let entries = [];
170
263
  try {
171
264
  entries = await import_promises2.default.readdir(outDir);
@@ -174,6 +267,20 @@ var listAiChunkFiles = async (params) => {
174
267
  }
175
268
  return entries.filter((name) => name.startsWith(prefix) && name.endsWith(".jsonl")).map((name) => import_path2.default.join(outDir, name)).sort();
176
269
  };
270
+ var listAiChunkRunIds = async (params) => {
271
+ const { strategyName, outDir = DEFAULT_DIR } = params;
272
+ let entries = [];
273
+ try {
274
+ entries = await import_promises2.default.readdir(outDir);
275
+ } catch {
276
+ return [];
277
+ }
278
+ return [
279
+ ...new Set(
280
+ entries.map((name) => getRunIdFromAiChunkFileName(strategyName, name)).filter(Boolean)
281
+ )
282
+ ].sort();
283
+ };
177
284
  var listAiChunkStrategies = async (params) => {
178
285
  const outDir = params?.outDir ?? DEFAULT_DIR;
179
286
  let entries = [];
@@ -272,7 +379,7 @@ var readNextNonEmptyLine = async (iterator) => {
272
379
  }
273
380
  }
274
381
  };
275
- var mergeSortedRuns = async (params) => {
382
+ var mergeSortedRunBatch = async (params) => {
276
383
  const { runPaths, outPath } = params;
277
384
  await import_promises2.default.mkdir(import_path2.default.dirname(outPath), { recursive: true });
278
385
  const output = (0, import_fs2.createWriteStream)(outPath, { encoding: "utf8" });
@@ -284,7 +391,7 @@ var mergeSortedRuns = async (params) => {
284
391
  try {
285
392
  for (let runIndex = 0; runIndex < runPaths.length; runIndex += 1) {
286
393
  const runPath = runPaths[runIndex];
287
- const reader = import_node_readline.default.createInterface({
394
+ const reader = import_node_readline2.default.createInterface({
288
395
  input: (0, import_node_fs.createReadStream)(runPath, { encoding: "utf8" }),
289
396
  crlfDelay: Infinity
290
397
  });
@@ -333,12 +440,58 @@ var mergeSortedRuns = async (params) => {
333
440
  await outputDone;
334
441
  }
335
442
  };
443
+ var mergeSortedRuns = async (params) => {
444
+ const {
445
+ runPaths,
446
+ outPath,
447
+ tempDir,
448
+ maxOpenRuns = AI_MERGE_SORT_MAX_OPEN_RUNS
449
+ } = params;
450
+ const resolvedMaxOpenRuns = Math.max(2, Math.trunc(maxOpenRuns));
451
+ let currentRunPaths = [...runPaths];
452
+ let passIndex = 0;
453
+ while (currentRunPaths.length > resolvedMaxOpenRuns) {
454
+ const nextRunPaths = [];
455
+ for (let groupStart = 0, groupIndex = 0; groupStart < currentRunPaths.length; groupStart += resolvedMaxOpenRuns, groupIndex += 1) {
456
+ const groupRunPaths = currentRunPaths.slice(
457
+ groupStart,
458
+ groupStart + resolvedMaxOpenRuns
459
+ );
460
+ if (groupRunPaths.length === 1) {
461
+ nextRunPaths.push(groupRunPaths[0]);
462
+ continue;
463
+ }
464
+ const passPath = import_path2.default.join(
465
+ tempDir,
466
+ `merge-pass-${String(passIndex).padStart(3, "0")}-${String(
467
+ groupIndex
468
+ ).padStart(6, "0")}.jsonl`
469
+ );
470
+ await mergeSortedRunBatch({
471
+ runPaths: groupRunPaths,
472
+ outPath: passPath
473
+ });
474
+ nextRunPaths.push(passPath);
475
+ await Promise.all(
476
+ groupRunPaths.map((runPath) => import_promises2.default.rm(runPath, { force: true }))
477
+ );
478
+ }
479
+ currentRunPaths = nextRunPaths;
480
+ passIndex += 1;
481
+ }
482
+ await mergeSortedRunBatch({
483
+ runPaths: currentRunPaths,
484
+ outPath
485
+ });
486
+ };
336
487
  var mergeAiJsonlFiles = async (params) => {
337
488
  const {
338
489
  filePaths,
339
490
  outPath,
340
491
  maxRowsInMemory = AI_MERGE_SORT_RUN_MAX_ROWS,
341
- maxBytesInMemory = AI_MERGE_SORT_RUN_MAX_BYTES
492
+ maxBytesInMemory = AI_MERGE_SORT_RUN_MAX_BYTES,
493
+ maxOpenRuns = AI_MERGE_SORT_MAX_OPEN_RUNS,
494
+ shouldIncludeRow
342
495
  } = params;
343
496
  const tempDir = import_path2.default.join(
344
497
  import_path2.default.dirname(outPath),
@@ -367,7 +520,7 @@ var mergeAiJsonlFiles = async (params) => {
367
520
  try {
368
521
  await import_promises2.default.mkdir(tempDir, { recursive: true });
369
522
  for (const filePath of filePaths) {
370
- const reader = import_node_readline.default.createInterface({
523
+ const reader = import_node_readline2.default.createInterface({
371
524
  input: (0, import_node_fs.createReadStream)(filePath, { encoding: "utf8" }),
372
525
  crlfDelay: Infinity
373
526
  });
@@ -377,9 +530,13 @@ var mergeAiJsonlFiles = async (params) => {
377
530
  if (!trimmed) {
378
531
  continue;
379
532
  }
533
+ const row = parseAiDatasetLine(trimmed, filePath);
534
+ if (shouldIncludeRow && !shouldIncludeRow(row)) {
535
+ continue;
536
+ }
380
537
  batch.push({
381
538
  line: trimmed,
382
- sortKey: getAiDatasetSortKey(parseAiDatasetLine(trimmed, filePath)),
539
+ sortKey: getAiDatasetSortKey(row),
383
540
  sourceIndex
384
541
  });
385
542
  sourceIndex += 1;
@@ -400,37 +557,145 @@ var mergeAiJsonlFiles = async (params) => {
400
557
  }
401
558
  await mergeSortedRuns({
402
559
  runPaths,
403
- outPath
560
+ outPath,
561
+ tempDir,
562
+ maxOpenRuns
404
563
  });
405
564
  } finally {
406
565
  await import_promises2.default.rm(tempDir, { recursive: true, force: true });
407
566
  }
408
567
  };
568
+ var getAiDatasetPartPath = (filePath, partIndex) => {
569
+ const parsed = import_path2.default.parse(filePath);
570
+ return import_path2.default.join(parsed.dir, `${parsed.name}-part${partIndex}${parsed.ext}`);
571
+ };
572
+ var getPartWindowEndExclusive = (timestamp, monthsPerPart) => {
573
+ const partStart = startOfMonth(new Date(timestamp));
574
+ return addMonths(partStart, Math.max(1, monthsPerPart)).getTime();
575
+ };
576
+ var splitAiMergedDatasetFile = async (params) => {
577
+ const { filePath, monthsPerPart = 2 } = params;
578
+ const resolvedMonthsPerPart = Math.max(1, Math.trunc(monthsPerPart));
579
+ const reader = import_node_readline2.default.createInterface({
580
+ input: (0, import_node_fs.createReadStream)(filePath, { encoding: "utf8" }),
581
+ crlfDelay: Infinity
582
+ });
583
+ const partPaths = [];
584
+ let currentPartIndex = 0;
585
+ let currentPartPath = "";
586
+ let currentPartEndExclusive = Number.NaN;
587
+ let currentWriter = null;
588
+ let currentWriterDone = null;
589
+ let rowCount = 0;
590
+ const openPart = async (timestamp) => {
591
+ currentPartIndex += 1;
592
+ currentPartPath = getAiDatasetPartPath(filePath, currentPartIndex);
593
+ currentPartEndExclusive = getPartWindowEndExclusive(
594
+ timestamp,
595
+ resolvedMonthsPerPart
596
+ );
597
+ currentWriter = (0, import_fs2.createWriteStream)(currentPartPath, { encoding: "utf8" });
598
+ currentWriterDone = Promise.all([
599
+ (0, import_events2.once)(currentWriter, "finish"),
600
+ (0, import_events2.once)(currentWriter, "close")
601
+ ]);
602
+ partPaths.push(currentPartPath);
603
+ };
604
+ const closeCurrentWriter = async () => {
605
+ if (!currentWriter || !currentWriterDone) {
606
+ return;
607
+ }
608
+ currentWriter.end();
609
+ await currentWriterDone;
610
+ currentWriter = null;
611
+ currentWriterDone = null;
612
+ };
613
+ const getCurrentWriter = () => {
614
+ if (!currentWriter) {
615
+ throw new Error(
616
+ `AI dataset part writer was not initialized for ${filePath}`
617
+ );
618
+ }
619
+ return currentWriter;
620
+ };
621
+ try {
622
+ for await (const line of reader) {
623
+ const trimmed = line.trim();
624
+ if (!trimmed) {
625
+ continue;
626
+ }
627
+ const row = parseAiDatasetLine(trimmed, filePath);
628
+ const timestamp = Number(row.timestamp);
629
+ const safeTimestamp = Number.isFinite(timestamp) ? timestamp : Date.now();
630
+ if (!currentWriter || !Number.isFinite(currentPartEndExclusive) || safeTimestamp >= currentPartEndExclusive) {
631
+ await closeCurrentWriter();
632
+ await openPart(safeTimestamp);
633
+ }
634
+ const writer = getCurrentWriter();
635
+ if (!writer.write(`${trimmed}
636
+ `)) {
637
+ await (0, import_events2.once)(writer, "drain");
638
+ }
639
+ rowCount += 1;
640
+ }
641
+ } finally {
642
+ reader.close();
643
+ await closeCurrentWriter();
644
+ }
645
+ if (partPaths.length <= 1) {
646
+ for (const partPath of partPaths) {
647
+ await import_promises2.default.rm(partPath, { force: true });
648
+ }
649
+ return {
650
+ partPaths: [filePath],
651
+ partCount: partPaths.length || (rowCount > 0 ? 1 : 0),
652
+ rowCount,
653
+ splitApplied: false
654
+ };
655
+ }
656
+ await import_promises2.default.rm(filePath, { force: true });
657
+ return {
658
+ partPaths,
659
+ partCount: partPaths.length,
660
+ rowCount,
661
+ splitApplied: true
662
+ };
663
+ };
409
664
  var readAiDatasetRows = async (params) => {
410
- const { filePath, limitFromEnd = 0, skipFromEnd = 0 } = params;
665
+ const { limitFromEnd = 0, skipFromEnd = 0 } = params;
666
+ const resolvedFilePaths = resolveAiDatasetFilePaths(params);
667
+ if (!resolvedFilePaths.length) {
668
+ return {
669
+ rows: [],
670
+ totalRows: 0
671
+ };
672
+ }
411
673
  const rows = [];
412
674
  const recentLines = [];
413
675
  let totalRows = 0;
414
676
  const recentWindowLimit = limitFromEnd > 0 ? limitFromEnd + Math.max(0, skipFromEnd) : 0;
415
- const reader = import_node_readline.default.createInterface({
416
- input: (0, import_node_fs.createReadStream)(filePath, { encoding: "utf8" }),
417
- crlfDelay: Infinity
418
- });
419
- for await (const line of reader) {
420
- const trimmed = line.trim();
421
- if (!trimmed) {
422
- continue;
423
- }
424
- totalRows += 1;
425
- if (limitFromEnd > 0) {
426
- if (recentLines.length === recentWindowLimit) {
427
- recentLines.shift();
677
+ for (const currentFilePath of resolvedFilePaths) {
678
+ const reader = import_node_readline2.default.createInterface({
679
+ input: (0, import_node_fs.createReadStream)(currentFilePath, { encoding: "utf8" }),
680
+ crlfDelay: Infinity
681
+ });
682
+ for await (const line of reader) {
683
+ const trimmed = line.trim();
684
+ if (!trimmed) {
685
+ continue;
686
+ }
687
+ totalRows += 1;
688
+ if (limitFromEnd > 0) {
689
+ if (recentLines.length === recentWindowLimit) {
690
+ recentLines.shift();
691
+ }
692
+ recentLines.push(trimmed);
693
+ } else {
694
+ const row = JSON.parse(trimmed);
695
+ rows.push(row);
428
696
  }
429
- recentLines.push(trimmed);
430
- } else {
431
- const row = JSON.parse(trimmed);
432
- rows.push(row);
433
697
  }
698
+ reader.close();
434
699
  }
435
700
  const effectiveSkip = Math.max(0, skipFromEnd);
436
701
  const selectedRecentLines = effectiveSkip > 0 ? recentLines.slice(0, Math.max(0, recentLines.length - effectiveSkip)) : recentLines;
@@ -440,17 +705,128 @@ var readAiDatasetRows = async (params) => {
440
705
  totalRows
441
706
  };
442
707
  };
708
+ var resolveAiDatasetFilePaths = (params) => (Array.isArray(params.filePaths) && params.filePaths.length ? params.filePaths : params.filePath ? [params.filePath] : []).map((item) => import_path2.default.resolve(item));
709
+ var countNonEmptyAiDatasetRows = async (filePath) => {
710
+ let totalRows = 0;
711
+ const reader = import_node_readline2.default.createInterface({
712
+ input: (0, import_node_fs.createReadStream)(filePath, { encoding: "utf8" }),
713
+ crlfDelay: Infinity
714
+ });
715
+ for await (const line of reader) {
716
+ if (line.trim()) {
717
+ totalRows += 1;
718
+ }
719
+ }
720
+ reader.close();
721
+ return totalRows;
722
+ };
723
+ var countAiDatasetRows = async (params) => {
724
+ const { limitFromEnd = 0, skipFromEnd = 0 } = params;
725
+ const resolvedFilePaths = resolveAiDatasetFilePaths(params);
726
+ let totalRows = 0;
727
+ for (const filePath of resolvedFilePaths) {
728
+ totalRows += await countNonEmptyAiDatasetRows(filePath);
729
+ }
730
+ const effectiveSkip = Math.max(0, skipFromEnd);
731
+ const selectedRows = limitFromEnd > 0 ? Math.min(limitFromEnd, Math.max(0, totalRows - effectiveSkip)) : Math.max(0, totalRows - effectiveSkip);
732
+ return {
733
+ totalRows,
734
+ selectedRows
735
+ };
736
+ };
737
+ var streamAiDatasetRows = async (params) => {
738
+ const { limitFromEnd = 0, skipFromEnd = 0, onRow } = params;
739
+ const resolvedFilePaths = resolveAiDatasetFilePaths(params);
740
+ if (!resolvedFilePaths.length) {
741
+ return {
742
+ totalRows: 0,
743
+ selectedRows: 0
744
+ };
745
+ }
746
+ const effectiveSkip = Math.max(0, skipFromEnd);
747
+ let totalRows = 0;
748
+ let selectedRows = 0;
749
+ if (limitFromEnd > 0) {
750
+ const recentLines = [];
751
+ const recentWindowLimit = limitFromEnd + effectiveSkip;
752
+ for (const currentFilePath of resolvedFilePaths) {
753
+ const reader = import_node_readline2.default.createInterface({
754
+ input: (0, import_node_fs.createReadStream)(currentFilePath, { encoding: "utf8" }),
755
+ crlfDelay: Infinity
756
+ });
757
+ for await (const line of reader) {
758
+ const trimmed = line.trim();
759
+ if (!trimmed) {
760
+ continue;
761
+ }
762
+ totalRows += 1;
763
+ if (recentLines.length === recentWindowLimit) {
764
+ recentLines.shift();
765
+ }
766
+ recentLines.push(trimmed);
767
+ }
768
+ reader.close();
769
+ }
770
+ const selectedRecentLines = effectiveSkip > 0 ? recentLines.slice(0, Math.max(0, recentLines.length - effectiveSkip)) : recentLines;
771
+ for (const line of selectedRecentLines) {
772
+ await onRow(JSON.parse(line), selectedRows);
773
+ selectedRows += 1;
774
+ }
775
+ return {
776
+ totalRows,
777
+ selectedRows
778
+ };
779
+ }
780
+ const trailingSkipBuffer = [];
781
+ for (const currentFilePath of resolvedFilePaths) {
782
+ const reader = import_node_readline2.default.createInterface({
783
+ input: (0, import_node_fs.createReadStream)(currentFilePath, { encoding: "utf8" }),
784
+ crlfDelay: Infinity
785
+ });
786
+ for await (const line of reader) {
787
+ const trimmed = line.trim();
788
+ if (!trimmed) {
789
+ continue;
790
+ }
791
+ totalRows += 1;
792
+ if (effectiveSkip > 0) {
793
+ trailingSkipBuffer.push(trimmed);
794
+ if (trailingSkipBuffer.length <= effectiveSkip) {
795
+ continue;
796
+ }
797
+ const selectedLine = trailingSkipBuffer.shift();
798
+ if (!selectedLine) {
799
+ continue;
800
+ }
801
+ await onRow(JSON.parse(selectedLine), selectedRows);
802
+ selectedRows += 1;
803
+ continue;
804
+ }
805
+ await onRow(JSON.parse(trimmed), selectedRows);
806
+ selectedRows += 1;
807
+ }
808
+ reader.close();
809
+ }
810
+ return {
811
+ totalRows,
812
+ selectedRows
813
+ };
814
+ };
443
815
  // Annotate the CommonJS export names for ESM import in node:
444
816
  0 && (module.exports = {
445
817
  appendAiDatasetRow,
446
818
  closeAiDatasetWriter,
447
819
  closeAllAiDatasetWriters,
820
+ countAiDatasetRows,
448
821
  flushAiDatasetWriter,
449
822
  getAiChunkFilePath,
450
823
  listAiChunkFiles,
824
+ listAiChunkRunIds,
451
825
  listAiChunkStrategies,
452
826
  mergeAiJsonlFiles,
453
827
  mergeJsonlFiles,
454
828
  readAiDatasetRows,
829
+ splitAiMergedDatasetFile,
830
+ streamAiDatasetRows,
455
831
  toFileToken
456
832
  });