@blackwell-systems/gcf 0.5.0 → 0.6.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.
- package/README.md +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/stream_generic.d.ts +38 -0
- package/dist/stream_generic.d.ts.map +1 -0
- package/dist/stream_generic.js +105 -0
- package/dist/stream_generic.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/stream_generic.ts +127 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
# gcf-typescript
|
|
8
8
|
|
|
9
|
-
TypeScript implementation of [GCF
|
|
9
|
+
TypeScript implementation of [GCF](https://gcformat.com/) — the most token-efficient wire format for LLMs. A drop-in alternative to JSON and TOON for any structured data.
|
|
10
10
|
|
|
11
11
|
**79% fewer input tokens than JSON. 75% fewer output tokens. 52% smaller than TOON. 100% LLM comprehension at 500 symbols, where JSON scores 76.9% and TOON scores 92.3%.**
|
|
12
12
|
|
package/dist/index.d.ts
CHANGED
|
@@ -7,4 +7,5 @@ export { encodeDelta } from './delta.js';
|
|
|
7
7
|
export { encodeGeneric } from './generic.js';
|
|
8
8
|
export { decodeGeneric } from './decode_generic.js';
|
|
9
9
|
export { StreamEncoder, type StreamWriter, type StreamOptions } from './stream.js';
|
|
10
|
+
export { GenericStreamEncoder } from './stream_generic.js';
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,4 +6,5 @@ export { encodeDelta } from './delta.js';
|
|
|
6
6
|
export { encodeGeneric } from './generic.js';
|
|
7
7
|
export { decodeGeneric } from './decode_generic.js';
|
|
8
8
|
export { StreamEncoder } from './stream.js';
|
|
9
|
+
export { GenericStreamEncoder } from './stream_generic.js';
|
|
9
10
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAyC,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAyC,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { StreamWriter } from './stream.js';
|
|
2
|
+
/**
|
|
3
|
+
* GenericStreamEncoder writes GCF tabular output incrementally as rows arrive.
|
|
4
|
+
* Zero buffering: each row is written immediately. A trailer summary is
|
|
5
|
+
* emitted on close() with the final counts.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const enc = new GenericStreamEncoder({ write: (s) => process.stdout.write(s) });
|
|
10
|
+
* enc.beginArray('employees', ['id', 'name', 'department', 'salary']);
|
|
11
|
+
* enc.writeRow([1, 'Alice', 'Engineering', 95000]);
|
|
12
|
+
* enc.writeRow([2, 'Bob', 'Sales', 72000]);
|
|
13
|
+
* enc.endArray();
|
|
14
|
+
* enc.close();
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare class GenericStreamEncoder {
|
|
18
|
+
private readonly writer;
|
|
19
|
+
private sections;
|
|
20
|
+
private current;
|
|
21
|
+
constructor(writer: StreamWriter);
|
|
22
|
+
/** Start a tabular array section with deferred count [?]. */
|
|
23
|
+
beginArray(name: string, fields: string[]): void;
|
|
24
|
+
/** Emit a single pipe-separated row immediately. */
|
|
25
|
+
writeRow(values: unknown[]): void;
|
|
26
|
+
/** Close the current array section and record its count. */
|
|
27
|
+
endArray(): void;
|
|
28
|
+
/** Emit a key=value line immediately. */
|
|
29
|
+
writeKV(key: string, value: unknown): void;
|
|
30
|
+
/** Start a nested object section (## key). */
|
|
31
|
+
writeSection(name: string): void;
|
|
32
|
+
/** Emit a primitive array inline: name[N]: val1,val2,val3 */
|
|
33
|
+
writeInlineArray(name: string, values: unknown[]): void;
|
|
34
|
+
/** Emit the ## _summary trailer with final counts. Must be called after all data. */
|
|
35
|
+
close(): void;
|
|
36
|
+
private endArrayInternal;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=stream_generic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream_generic.d.ts","sourceRoot":"","sources":["../src/stream_generic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAahD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,OAAO,CAA4B;gBAE/B,MAAM,EAAE,YAAY;IAIhC,6DAA6D;IAC7D,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI;IAQhD,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI;IASjC,4DAA4D;IAC5D,QAAQ,IAAI,IAAI;IAIhB,yCAAyC;IACzC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAI1C,8CAA8C;IAC9C,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAOhC,6DAA6D;IAC7D,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI;IAKvD,qFAAqF;IACrF,KAAK,IAAI,IAAI;IAgBb,OAAO,CAAC,gBAAgB;CAOzB"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenericStreamEncoder writes GCF tabular output incrementally as rows arrive.
|
|
3
|
+
* Zero buffering: each row is written immediately. A trailer summary is
|
|
4
|
+
* emitted on close() with the final counts.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const enc = new GenericStreamEncoder({ write: (s) => process.stdout.write(s) });
|
|
9
|
+
* enc.beginArray('employees', ['id', 'name', 'department', 'salary']);
|
|
10
|
+
* enc.writeRow([1, 'Alice', 'Engineering', 95000]);
|
|
11
|
+
* enc.writeRow([2, 'Bob', 'Sales', 72000]);
|
|
12
|
+
* enc.endArray();
|
|
13
|
+
* enc.close();
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export class GenericStreamEncoder {
|
|
17
|
+
writer;
|
|
18
|
+
sections = [];
|
|
19
|
+
current = null;
|
|
20
|
+
constructor(writer) {
|
|
21
|
+
this.writer = writer;
|
|
22
|
+
}
|
|
23
|
+
/** Start a tabular array section with deferred count [?]. */
|
|
24
|
+
beginArray(name, fields) {
|
|
25
|
+
if (this.current !== null) {
|
|
26
|
+
this.endArrayInternal();
|
|
27
|
+
}
|
|
28
|
+
this.writer.write(`## ${name} [?]{${fields.join(',')}}\n`);
|
|
29
|
+
this.current = { name, fields, count: 0 };
|
|
30
|
+
}
|
|
31
|
+
/** Emit a single pipe-separated row immediately. */
|
|
32
|
+
writeRow(values) {
|
|
33
|
+
if (this.current === null) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const parts = values.map(formatValue);
|
|
37
|
+
this.writer.write(`${parts.join('|')}\n`);
|
|
38
|
+
this.current.count++;
|
|
39
|
+
}
|
|
40
|
+
/** Close the current array section and record its count. */
|
|
41
|
+
endArray() {
|
|
42
|
+
this.endArrayInternal();
|
|
43
|
+
}
|
|
44
|
+
/** Emit a key=value line immediately. */
|
|
45
|
+
writeKV(key, value) {
|
|
46
|
+
this.writer.write(`${key}=${formatValue(value)}\n`);
|
|
47
|
+
}
|
|
48
|
+
/** Start a nested object section (## key). */
|
|
49
|
+
writeSection(name) {
|
|
50
|
+
if (this.current !== null) {
|
|
51
|
+
this.endArrayInternal();
|
|
52
|
+
}
|
|
53
|
+
this.writer.write(`## ${name}\n`);
|
|
54
|
+
}
|
|
55
|
+
/** Emit a primitive array inline: name[N]: val1,val2,val3 */
|
|
56
|
+
writeInlineArray(name, values) {
|
|
57
|
+
const parts = values.map(formatValue);
|
|
58
|
+
this.writer.write(`${name}[${values.length}]: ${parts.join(',')}\n`);
|
|
59
|
+
}
|
|
60
|
+
/** Emit the ## _summary trailer with final counts. Must be called after all data. */
|
|
61
|
+
close() {
|
|
62
|
+
if (this.current !== null) {
|
|
63
|
+
this.endArrayInternal();
|
|
64
|
+
}
|
|
65
|
+
if (this.sections.length === 0) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let totalRows = 0;
|
|
69
|
+
const sectionParts = [];
|
|
70
|
+
for (const s of this.sections) {
|
|
71
|
+
sectionParts.push(`${s.name}:${s.count}`);
|
|
72
|
+
totalRows += s.count;
|
|
73
|
+
}
|
|
74
|
+
this.writer.write(`## _summary rows=${totalRows} sections=${sectionParts.join(',')}\n`);
|
|
75
|
+
}
|
|
76
|
+
endArrayInternal() {
|
|
77
|
+
if (this.current === null) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.sections.push({ name: this.current.name, count: this.current.count });
|
|
81
|
+
this.current = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function formatValue(v) {
|
|
85
|
+
if (v === null || v === undefined) {
|
|
86
|
+
return '-';
|
|
87
|
+
}
|
|
88
|
+
if (typeof v === 'boolean') {
|
|
89
|
+
return v ? 'true' : 'false';
|
|
90
|
+
}
|
|
91
|
+
if (typeof v === 'number') {
|
|
92
|
+
return String(v);
|
|
93
|
+
}
|
|
94
|
+
if (typeof v === 'string') {
|
|
95
|
+
if (v === '') {
|
|
96
|
+
return '""';
|
|
97
|
+
}
|
|
98
|
+
if (v.includes('|') || v.includes('\n')) {
|
|
99
|
+
return `"${v.replace(/"/g, '\\"')}"`;
|
|
100
|
+
}
|
|
101
|
+
return v;
|
|
102
|
+
}
|
|
103
|
+
return String(v);
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=stream_generic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream_generic.js","sourceRoot":"","sources":["../src/stream_generic.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -7,3 +7,4 @@ export { encodeDelta } from './delta.js';
|
|
|
7
7
|
export { encodeGeneric } from './generic.js';
|
|
8
8
|
export { decodeGeneric } from './decode_generic.js';
|
|
9
9
|
export { StreamEncoder, type StreamWriter, type StreamOptions } from './stream.js';
|
|
10
|
+
export { GenericStreamEncoder } from './stream_generic.js';
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { StreamWriter } from './stream.js';
|
|
2
|
+
|
|
3
|
+
interface SectionCount {
|
|
4
|
+
name: string;
|
|
5
|
+
count: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ActiveArray {
|
|
9
|
+
name: string;
|
|
10
|
+
fields: string[];
|
|
11
|
+
count: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* GenericStreamEncoder writes GCF tabular output incrementally as rows arrive.
|
|
16
|
+
* Zero buffering: each row is written immediately. A trailer summary is
|
|
17
|
+
* emitted on close() with the final counts.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const enc = new GenericStreamEncoder({ write: (s) => process.stdout.write(s) });
|
|
22
|
+
* enc.beginArray('employees', ['id', 'name', 'department', 'salary']);
|
|
23
|
+
* enc.writeRow([1, 'Alice', 'Engineering', 95000]);
|
|
24
|
+
* enc.writeRow([2, 'Bob', 'Sales', 72000]);
|
|
25
|
+
* enc.endArray();
|
|
26
|
+
* enc.close();
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export class GenericStreamEncoder {
|
|
30
|
+
private readonly writer: StreamWriter;
|
|
31
|
+
private sections: SectionCount[] = [];
|
|
32
|
+
private current: ActiveArray | null = null;
|
|
33
|
+
|
|
34
|
+
constructor(writer: StreamWriter) {
|
|
35
|
+
this.writer = writer;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Start a tabular array section with deferred count [?]. */
|
|
39
|
+
beginArray(name: string, fields: string[]): void {
|
|
40
|
+
if (this.current !== null) {
|
|
41
|
+
this.endArrayInternal();
|
|
42
|
+
}
|
|
43
|
+
this.writer.write(`## ${name} [?]{${fields.join(',')}}\n`);
|
|
44
|
+
this.current = { name, fields, count: 0 };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Emit a single pipe-separated row immediately. */
|
|
48
|
+
writeRow(values: unknown[]): void {
|
|
49
|
+
if (this.current === null) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const parts = values.map(formatValue);
|
|
53
|
+
this.writer.write(`${parts.join('|')}\n`);
|
|
54
|
+
this.current.count++;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Close the current array section and record its count. */
|
|
58
|
+
endArray(): void {
|
|
59
|
+
this.endArrayInternal();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Emit a key=value line immediately. */
|
|
63
|
+
writeKV(key: string, value: unknown): void {
|
|
64
|
+
this.writer.write(`${key}=${formatValue(value)}\n`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Start a nested object section (## key). */
|
|
68
|
+
writeSection(name: string): void {
|
|
69
|
+
if (this.current !== null) {
|
|
70
|
+
this.endArrayInternal();
|
|
71
|
+
}
|
|
72
|
+
this.writer.write(`## ${name}\n`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Emit a primitive array inline: name[N]: val1,val2,val3 */
|
|
76
|
+
writeInlineArray(name: string, values: unknown[]): void {
|
|
77
|
+
const parts = values.map(formatValue);
|
|
78
|
+
this.writer.write(`${name}[${values.length}]: ${parts.join(',')}\n`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Emit the ## _summary trailer with final counts. Must be called after all data. */
|
|
82
|
+
close(): void {
|
|
83
|
+
if (this.current !== null) {
|
|
84
|
+
this.endArrayInternal();
|
|
85
|
+
}
|
|
86
|
+
if (this.sections.length === 0) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
let totalRows = 0;
|
|
90
|
+
const sectionParts: string[] = [];
|
|
91
|
+
for (const s of this.sections) {
|
|
92
|
+
sectionParts.push(`${s.name}:${s.count}`);
|
|
93
|
+
totalRows += s.count;
|
|
94
|
+
}
|
|
95
|
+
this.writer.write(`## _summary rows=${totalRows} sections=${sectionParts.join(',')}\n`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private endArrayInternal(): void {
|
|
99
|
+
if (this.current === null) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.sections.push({ name: this.current.name, count: this.current.count });
|
|
103
|
+
this.current = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function formatValue(v: unknown): string {
|
|
108
|
+
if (v === null || v === undefined) {
|
|
109
|
+
return '-';
|
|
110
|
+
}
|
|
111
|
+
if (typeof v === 'boolean') {
|
|
112
|
+
return v ? 'true' : 'false';
|
|
113
|
+
}
|
|
114
|
+
if (typeof v === 'number') {
|
|
115
|
+
return String(v);
|
|
116
|
+
}
|
|
117
|
+
if (typeof v === 'string') {
|
|
118
|
+
if (v === '') {
|
|
119
|
+
return '""';
|
|
120
|
+
}
|
|
121
|
+
if (v.includes('|') || v.includes('\n')) {
|
|
122
|
+
return `"${v.replace(/"/g, '\\"')}"`;
|
|
123
|
+
}
|
|
124
|
+
return v;
|
|
125
|
+
}
|
|
126
|
+
return String(v);
|
|
127
|
+
}
|