Skip to content

Commit 063c647

Browse files
committed
streaming
1 parent db9b78b commit 063c647

6 files changed

Lines changed: 58 additions & 27 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,11 +359,12 @@ Notes:
359359
- for JSON requests, newline characters inside string literals must be escaped as `\\n`
360360
- use `/v1/generate/fromschema` when you want to paste raw multiline text directly
361361

362-
Streaming notes for generation:
362+
Streaming notes for CLI/core generation:
363363

364364
- stream mode supports: `csv`, `jsonl`, `dsv`, `json`, `xml`
365365
- `json` stream mode emits a valid JSON array payload
366366
- incompatible format options in stream mode are warned and ignored where needed
367+
- REST API generation endpoints currently run in buffered mode
367368

368369
OpenAPI spec:
369370

apps/api/src/openapi.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const openApiDocument = {
3333
post: {
3434
summary: 'Generate data from text spec with selectable response shape',
3535
description:
36-
'Streaming generation is available for csv, jsonl, dsv, json and xml. In stream mode, incompatible format options are reported as warnings and ignored where needed.',
36+
'Generation requests are currently processed in buffered mode for all formats. Stream-mode behavior is available in the core helper and CLI, not this REST endpoint.',
3737
requestBody: {
3838
required: true,
3939
content: {

docs-src/docs/070-interfaces-and-deployment/030-rest-api.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,10 @@ Both endpoints generate data from the same schema language and output formats. T
148148
- `stream` is accepted for compatibility and ignored
149149
- `inputFormat` is normalized (trimmed and lower-cased), so values like `" csv "` are accepted
150150

151-
Generation streaming behavior:
151+
Generation mode behavior:
152152

153-
- stream mode supports: `csv`, `jsonl`, `dsv`, `json`, `xml`
154-
- `json` stream mode emits a valid JSON array payload
155-
- if stream mode cannot honor some format options, generation continues and warnings are reported
153+
- REST generation endpoints currently run in buffered mode.
154+
- Stream-mode generation behavior is available via the core helper/CLI paths, not via `/v1/generate`.
156155

157156
## Schema Formatting
158157

package-lock.json

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/src/index.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -553,15 +553,16 @@ function quoteCsvValue(value, { quoteChar, escapeChar }) {
553553
}
554554

555555
function rowToCsv(headers, row, csvSettings) {
556-
return headers.map((header) => quoteCsvValue(row[header], csvSettings)).join(',');
556+
return headers.map((_, index) => quoteCsvValue(row[index], csvSettings)).join(',');
557557
}
558558

559559
function rowToDsv(headers, row, dsvSettings) {
560-
return headers.map((header) => quoteCsvValue(row[header], dsvSettings)).join(dsvSettings.delimiter);
560+
return headers.map((_, index) => quoteCsvValue(row[index], dsvSettings)).join(dsvSettings.delimiter);
561561
}
562562

563-
function rowToJsonLine(row) {
564-
return JSON.stringify(row);
563+
function rowToJsonLine(headers, rowArray, options = {}) {
564+
const rowObject = rowArrayToObject(headers, rowArray, options);
565+
return JSON.stringify(rowObject);
565566
}
566567

567568
function rowArrayToObject(headers, rowArray, options = {}) {
@@ -571,10 +572,13 @@ function rowArrayToObject(headers, rowArray, options = {}) {
571572
const header = headers[i];
572573
const value = rowArray[i] === undefined || rowArray[i] === null ? '' : rowArray[i];
573574
if (makeNumbersNumeric) {
574-
if (value * 1 == 0) {
575-
rowObject[header] = 0;
575+
const text = String(value);
576+
if (text.trim().length === 0) {
577+
rowObject[header] = value;
578+
} else if (Number.isFinite(Number(text))) {
579+
rowObject[header] = Number(text);
576580
} else {
577-
rowObject[header] = value == value * 1 ? value * 1 : value;
581+
rowObject[header] = value;
578582
}
579583
} else {
580584
rowObject[header] = value;
@@ -789,7 +793,7 @@ export async function streamFromTextSpec({
789793
} else if (outputFormat === 'dsv') {
790794
await onChunk(rowToDsv(headers, rowArray, dsvSettings));
791795
} else if (outputFormat === 'jsonl') {
792-
await onChunk(rowToJsonLine(rowArray));
796+
await onChunk(rowToJsonLine(headers, rowArray, options));
793797
} else if (outputFormat === 'json') {
794798
const rowObject = rowArrayToObject(headers, rowArray, options);
795799
await onChunk(index === 0 ? JSON.stringify(rowObject) : `,${JSON.stringify(rowObject)}`);

packages/core/src/tests/core-api/streamFromTextSpec.test.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ test('streamFromTextSpec streams csv rows with header', async () => {
1414
expect(result.ok).toBe(true);
1515
expect(chunks.length).toBe(3);
1616
expect(chunks[0]).toContain('"Name"');
17+
expect(chunks[1]).toContain('"Bob"');
18+
expect(chunks[2]).toContain('"Bob"');
1719
});
1820

1921
test('streamFromTextSpec streams jsonl rows', async () => {
@@ -30,7 +32,10 @@ test('streamFromTextSpec streams jsonl rows', async () => {
3032
expect(result.ok).toBe(true);
3133
expect(chunks.length).toBe(2);
3234
for (const chunk of chunks) {
33-
expect(() => JSON.parse(chunk)).not.toThrow();
35+
const parsed = JSON.parse(chunk);
36+
expect(Array.isArray(parsed)).toBe(false);
37+
expect(parsed).toHaveProperty('Name');
38+
expect(parsed.Name).toBe('Bob');
3439
}
3540
});
3641

@@ -73,6 +78,8 @@ test('streamFromTextSpec streams dsv rows with delimiter and header', async () =
7378
expect(result.ok).toBe(true);
7479
expect(chunks.length).toBe(3);
7580
expect(chunks[0]).toContain('"Name"');
81+
expect(chunks[1]).toContain('"Bob"');
82+
expect(chunks[2]).toContain('"Bob"');
7683
});
7784

7885
test('streamFromTextSpec streams json as valid array payload', async () => {
@@ -91,6 +98,8 @@ test('streamFromTextSpec streams json as valid array payload', async () => {
9198
expect(Array.isArray(parsed)).toBe(true);
9299
expect(parsed.length).toBe(2);
93100
expect(parsed[0]).toHaveProperty('Name');
101+
expect(parsed[0].Name).toBe('Bob');
102+
expect(parsed[1].Name).toBe('Bob');
94103
});
95104

96105
test('streamFromTextSpec streams xml as a well-formed document', async () => {
@@ -109,6 +118,7 @@ test('streamFromTextSpec streams xml as a well-formed document', async () => {
109118
expect(payload).toContain('<root>');
110119
expect(payload).toContain('</root>');
111120
expect(payload).toContain('<item>');
121+
expect(payload).toContain('<Name>Bob</Name>');
112122
});
113123

114124
test('streamFromTextSpec warns for json options that are unsupported in stream mode', async () => {
@@ -126,3 +136,20 @@ test('streamFromTextSpec warns for json options that are unsupported in stream m
126136
expect(Array.isArray(result.diagnostics.warnings)).toBe(true);
127137
expect(result.diagnostics.warnings.join(' ')).toContain('ignores option asObject');
128138
});
139+
140+
test('streamFromTextSpec json makeNumbersNumeric preserves empty strings', async () => {
141+
const chunks = [];
142+
const result = await streamFromTextSpec({
143+
textSpec: 'Quantity\n()',
144+
rowCount: 1,
145+
outputFormat: 'json',
146+
options: { makeNumbersNumeric: true },
147+
onChunk: async (chunk) => {
148+
chunks.push(chunk);
149+
},
150+
});
151+
152+
expect(result.ok).toBe(true);
153+
const parsed = JSON.parse(chunks.join(''));
154+
expect(parsed[0].Quantity).toBe('');
155+
});

0 commit comments

Comments
 (0)