@blackwell-systems/gcf 2.1.2 → 2.2.1
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 +6 -6
- package/dist/cjs/index.cjs +259 -40
- package/dist/cli.js +0 -0
- package/dist/decode_generic.js +92 -8
- package/dist/decode_generic.js.map +1 -1
- package/dist/generic.d.ts +9 -1
- package/dist/generic.d.ts.map +1 -1
- package/dist/generic.js +231 -35
- package/dist/generic.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/decode_generic.ts +97 -7
- package/src/generic.ts +232 -35
- package/src/index.ts +1 -1
- package/dist/cjs/browser.d.ts +0 -9
- package/dist/cjs/browser.js +0 -25
- package/dist/cjs/browser.js.map +0 -1
- package/dist/cjs/cli.d.ts +0 -5
- package/dist/cjs/cli.js +0 -136
- package/dist/cjs/cli.js.map +0 -1
- package/dist/cjs/constants.d.ts +0 -8
- package/dist/cjs/constants.js +0 -46
- package/dist/cjs/constants.js.map +0 -1
- package/dist/cjs/decode.d.ts +0 -5
- package/dist/cjs/decode.js +0 -197
- package/dist/cjs/decode.js.map +0 -1
- package/dist/cjs/decode_generic.d.ts +0 -4
- package/dist/cjs/decode_generic.js +0 -678
- package/dist/cjs/decode_generic.js.map +0 -1
- package/dist/cjs/delta.d.ts +0 -15
- package/dist/cjs/delta.js +0 -73
- package/dist/cjs/delta.js.map +0 -1
- package/dist/cjs/encode.d.ts +0 -5
- package/dist/cjs/encode.js +0 -89
- package/dist/cjs/encode.js.map +0 -1
- package/dist/cjs/generic.d.ts +0 -1
- package/dist/cjs/generic.js +0 -332
- package/dist/cjs/generic.js.map +0 -1
- package/dist/cjs/index.d.ts +0 -11
- package/dist/cjs/index.js +0 -32
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/packroot.d.ts +0 -13
- package/dist/cjs/packroot.js +0 -50
- package/dist/cjs/packroot.js.map +0 -1
- package/dist/cjs/scalar.d.ts +0 -26
- package/dist/cjs/scalar.js +0 -339
- package/dist/cjs/scalar.js.map +0 -1
- package/dist/cjs/session.d.ts +0 -30
- package/dist/cjs/session.js +0 -140
- package/dist/cjs/session.js.map +0 -1
- package/dist/cjs/stream.d.ts +0 -66
- package/dist/cjs/stream.js +0 -127
- package/dist/cjs/stream.js.map +0 -1
- package/dist/cjs/stream_generic.d.ts +0 -37
- package/dist/cjs/stream_generic.js +0 -87
- package/dist/cjs/stream_generic.js.map +0 -1
- package/dist/cjs/types.d.ts +0 -75
- package/dist/cjs/types.js +0 -3
- package/dist/cjs/types.js.map +0 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
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
|
-
**100% comprehension on every frontier model tested.
|
|
11
|
+
**100% comprehension on every frontier model tested. 29% fewer tokens than TOON, 56% fewer than JSON across 16 datasets. 91.2% on structurally complex code graphs (vs TOON 68.2%, JSON 53.4%). 2,400+ LLM evaluations. Zero training.**
|
|
12
12
|
|
|
13
13
|
Docs: [gcformat.com](https://gcformat.com/) · [Playground](https://gcformat.com/playground.html) · [GCF vs TOON](https://gcformat.com/guide/vs-toon.html)
|
|
14
14
|
|
|
@@ -192,16 +192,16 @@ Works on objects, arrays, and primitives. Arrays of uniform objects get tabular
|
|
|
192
192
|
|
|
193
193
|
## Benchmarks
|
|
194
194
|
|
|
195
|
-
|
|
195
|
+
2,400+ LLM evaluations across 10 models, 3 providers, and 51 independent test runs.
|
|
196
196
|
|
|
197
197
|
| | GCF | TOON | JSON |
|
|
198
198
|
|---|---|---|---|
|
|
199
|
-
| **Comprehension** (23 runs, 10 models) | **
|
|
199
|
+
| **Comprehension** (23 runs, 10 models) | **91.2%** | 68.2% | 53.4% |
|
|
200
200
|
| **Generation** (28 runs, 9 models) | **5/5** | 1.0/5 | 5.0/5 |
|
|
201
201
|
| **Input tokens** (500 symbols) | **11,090** | 16,378 | 53,341 |
|
|
202
202
|
| **Output tokens** (100 symbols) | **5,976** | 8,937 | 16,121 |
|
|
203
203
|
|
|
204
|
-
GCF wins
|
|
204
|
+
GCF wins 15/16 datasets on the expanded [token efficiency benchmark](https://github.com/blackwell-systems/toon/tree/gcf-comparison). Full results: [gcformat.com/guide/benchmarks](https://gcformat.com/guide/benchmarks.html)
|
|
205
205
|
|
|
206
206
|
## Implementations
|
|
207
207
|
|
|
@@ -220,9 +220,9 @@ GCF wins 13/15 datasets on the expanded [token efficiency benchmark](https://git
|
|
|
220
220
|
| n8n | `npm install n8n-nodes-gcf` | [gcf-n8n-nodes](https://github.com/blackwell-systems/gcf-n8n-nodes) (workflow encode/decode) |
|
|
221
221
|
| Tree-sitter | `npm install tree-sitter-gcf` | [tree-sitter-gcf](https://github.com/blackwell-systems/tree-sitter-gcf) |
|
|
222
222
|
|
|
223
|
-
Zero runtime dependencies. MIT licensed. All implementations support both generic profile (`encodeGeneric`) and graph profile (`encode`). CLI included in all 6 languages.
|
|
223
|
+
**Zero runtime dependencies. Permanently.** All six implementations depend only on their language's standard library. No transitive dependencies. No supply chain risk. This is a permanent commitment: GCF will never take on external runtime dependencies. MIT licensed. All implementations support both generic profile (`encodeGeneric`) and graph profile (`encode`). CLI included in all 6 languages.
|
|
224
224
|
|
|
225
|
-
**Specification:** [SPEC v3.
|
|
225
|
+
**Specification:** [SPEC v3.2 Stable](https://github.com/blackwell-systems/gcf/blob/main/SPEC.md) with 173 conformance fixtures, 43,000,000,000+ lossless round-trips verified across 5 formats and 6 languages. All implementations at v2.2.0+ (Go v1.3.0). Cross-language 6x6 matrix verified.
|
|
226
226
|
|
|
227
227
|
## License
|
|
228
228
|
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -790,30 +790,30 @@ function verifyDelta(baseSymbols, baseEdges, removedSymbols, addedSymbols, remov
|
|
|
790
790
|
function indent(depth) {
|
|
791
791
|
return " ".repeat(depth);
|
|
792
792
|
}
|
|
793
|
-
function encodeGeneric(data) {
|
|
793
|
+
function encodeGeneric(data, opts) {
|
|
794
794
|
let out = "GCF profile=generic\n";
|
|
795
|
-
out += encodeRootValue(data);
|
|
795
|
+
out += encodeRootValue(data, opts);
|
|
796
796
|
return out;
|
|
797
797
|
}
|
|
798
|
-
function encodeRootValue(v) {
|
|
798
|
+
function encodeRootValue(v, opts) {
|
|
799
799
|
if (v === null || v === void 0) return "=-\n";
|
|
800
|
-
if (Array.isArray(v)) return encodeRootArray(v);
|
|
801
|
-
if (typeof v === "object") return encodeObject(v, 0);
|
|
800
|
+
if (Array.isArray(v)) return encodeRootArray(v, opts);
|
|
801
|
+
if (typeof v === "object") return encodeObject(v, 0, opts);
|
|
802
802
|
return `=${formatScalar(v, 0)}
|
|
803
803
|
`;
|
|
804
804
|
}
|
|
805
|
-
function encodeObject(obj, depth) {
|
|
805
|
+
function encodeObject(obj, depth, opts) {
|
|
806
806
|
const prefix = indent(depth);
|
|
807
807
|
let out = "";
|
|
808
808
|
for (const key of Object.keys(obj)) {
|
|
809
809
|
const value = obj[key];
|
|
810
810
|
const fk = formatKey(key);
|
|
811
811
|
if (Array.isArray(value)) {
|
|
812
|
-
out += encodeNamedArray(fk, value, depth);
|
|
812
|
+
out += encodeNamedArray(fk, value, depth, opts);
|
|
813
813
|
} else if (typeof value === "object" && value !== null) {
|
|
814
814
|
out += `${prefix}## ${fk}
|
|
815
815
|
`;
|
|
816
|
-
out += encodeObject(value, depth + 1);
|
|
816
|
+
out += encodeObject(value, depth + 1, opts);
|
|
817
817
|
} else {
|
|
818
818
|
out += `${prefix}${fk}=${formatScalar(value, 0)}
|
|
819
819
|
`;
|
|
@@ -821,7 +821,7 @@ function encodeObject(obj, depth) {
|
|
|
821
821
|
}
|
|
822
822
|
return out;
|
|
823
823
|
}
|
|
824
|
-
function encodeRootArray(arr) {
|
|
824
|
+
function encodeRootArray(arr, opts) {
|
|
825
825
|
if (arr.length === 0) return "## [0]\n";
|
|
826
826
|
if (allPrimitives(arr)) {
|
|
827
827
|
const vals = arr.map((v) => formatScalar(v, 44));
|
|
@@ -829,10 +829,10 @@ function encodeRootArray(arr) {
|
|
|
829
829
|
`;
|
|
830
830
|
}
|
|
831
831
|
const fields = tabularFields(arr);
|
|
832
|
-
if (fields) return encodeTabular("## ", arr, fields, 0);
|
|
833
|
-
return encodeExpanded("## ", arr, 0);
|
|
832
|
+
if (fields) return encodeTabular("## ", arr, fields, 0, opts);
|
|
833
|
+
return encodeExpanded("## ", arr, 0, opts);
|
|
834
834
|
}
|
|
835
|
-
function encodeNamedArray(name, arr, depth) {
|
|
835
|
+
function encodeNamedArray(name, arr, depth, opts) {
|
|
836
836
|
const prefix = indent(depth);
|
|
837
837
|
if (arr.length === 0) return `${prefix}## ${name} [0]
|
|
838
838
|
`;
|
|
@@ -842,8 +842,8 @@ function encodeNamedArray(name, arr, depth) {
|
|
|
842
842
|
`;
|
|
843
843
|
}
|
|
844
844
|
const fields = tabularFields(arr);
|
|
845
|
-
if (fields) return encodeTabular(`${prefix}## ${name} `, arr, fields, depth);
|
|
846
|
-
return encodeExpanded(`${prefix}## ${name} `, arr, depth);
|
|
845
|
+
if (fields) return encodeTabular(`${prefix}## ${name} `, arr, fields, depth, opts);
|
|
846
|
+
return encodeExpanded(`${prefix}## ${name} `, arr, depth, opts);
|
|
847
847
|
}
|
|
848
848
|
function tabularFields(arr) {
|
|
849
849
|
if (arr.length === 0) return null;
|
|
@@ -918,25 +918,161 @@ function sharedArraySchema(arr, fieldName) {
|
|
|
918
918
|
}
|
|
919
919
|
return canonicalFields;
|
|
920
920
|
}
|
|
921
|
-
function
|
|
921
|
+
function analyzeFlattenable(arr, fieldName, parentPath) {
|
|
922
|
+
if (fieldName.includes(">")) return null;
|
|
923
|
+
let canonicalShape = null;
|
|
924
|
+
for (const item of arr) {
|
|
925
|
+
const obj = item;
|
|
926
|
+
if (!(fieldName in obj) || obj[fieldName] === null || obj[fieldName] === void 0) continue;
|
|
927
|
+
const v = obj[fieldName];
|
|
928
|
+
if (typeof v !== "object" || Array.isArray(v)) return null;
|
|
929
|
+
const keys = Object.keys(v);
|
|
930
|
+
if (!canonicalShape) {
|
|
931
|
+
canonicalShape = {};
|
|
932
|
+
for (const k of keys) {
|
|
933
|
+
if (k.includes(">")) return null;
|
|
934
|
+
const val = v[k];
|
|
935
|
+
if (val !== null && val !== void 0 && typeof val === "object" && !Array.isArray(val)) {
|
|
936
|
+
canonicalShape[k] = "nested";
|
|
937
|
+
} else if (Array.isArray(val)) {
|
|
938
|
+
return null;
|
|
939
|
+
} else {
|
|
940
|
+
canonicalShape[k] = "scalar";
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
} else {
|
|
944
|
+
if (keys.length !== Object.keys(canonicalShape).length) return null;
|
|
945
|
+
for (const k of keys) {
|
|
946
|
+
if (!(k in canonicalShape)) return null;
|
|
947
|
+
const val = v[k];
|
|
948
|
+
const expected = canonicalShape[k];
|
|
949
|
+
if (expected === "scalar") {
|
|
950
|
+
if (val !== null && val !== void 0 && typeof val === "object") return null;
|
|
951
|
+
} else if (expected === "nested") {
|
|
952
|
+
if (val !== null && val !== void 0) {
|
|
953
|
+
if (typeof val !== "object" || Array.isArray(val)) return null;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
if (!canonicalShape) return null;
|
|
960
|
+
const currentPath = parentPath ? parentPath + ">" + fieldName : fieldName;
|
|
961
|
+
const parentKeys = parentPath ? [...parentPath.split(">"), fieldName] : [fieldName];
|
|
962
|
+
const leaves = [];
|
|
963
|
+
for (const k of Object.keys(canonicalShape)) {
|
|
964
|
+
if (canonicalShape[k] === "scalar") {
|
|
965
|
+
leaves.push({ path: currentPath + ">" + k, keys: [...parentKeys, k] });
|
|
966
|
+
} else {
|
|
967
|
+
const subArr = arr.map((item) => {
|
|
968
|
+
const obj = item;
|
|
969
|
+
if (!(fieldName in obj) || obj[fieldName] === null || obj[fieldName] === void 0) return {};
|
|
970
|
+
return obj[fieldName];
|
|
971
|
+
});
|
|
972
|
+
const subLeaves = analyzeFlattenable(subArr, k, currentPath);
|
|
973
|
+
if (!subLeaves || subLeaves.length === 0) return null;
|
|
974
|
+
leaves.push(...subLeaves);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (leaves.length > 0) {
|
|
978
|
+
for (const item of arr) {
|
|
979
|
+
const obj = item;
|
|
980
|
+
if (!(fieldName in obj) || obj[fieldName] === null || obj[fieldName] === void 0) continue;
|
|
981
|
+
const allNull = leaves.every((leaf) => {
|
|
982
|
+
const val = resolveKeyChain(item, leaf.keys);
|
|
983
|
+
return val.exists && val.value === null;
|
|
984
|
+
});
|
|
985
|
+
if (allNull) return null;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return leaves;
|
|
989
|
+
}
|
|
990
|
+
function resolveKeyChain(item, keys) {
|
|
991
|
+
if (keys.length === 0) return { value: void 0, exists: false };
|
|
992
|
+
const obj = item;
|
|
993
|
+
if (typeof obj !== "object" || obj === null) return { value: void 0, exists: false };
|
|
994
|
+
if (!(keys[0] in obj)) return { value: void 0, exists: false };
|
|
995
|
+
let current = obj[keys[0]];
|
|
996
|
+
if (current === null || current === void 0) return { value: current, exists: true };
|
|
997
|
+
for (let i = 1; i < keys.length; i++) {
|
|
998
|
+
if (typeof current !== "object" || current === null) return { value: void 0, exists: false };
|
|
999
|
+
const c = current;
|
|
1000
|
+
if (!(keys[i] in c)) return { value: void 0, exists: false };
|
|
1001
|
+
current = c[keys[i]];
|
|
1002
|
+
}
|
|
1003
|
+
return { value: current, exists: true };
|
|
1004
|
+
}
|
|
1005
|
+
function encodeTabular(headerPrefix, arr, fields, depth, opts) {
|
|
922
1006
|
const prefix = indent(depth);
|
|
1007
|
+
const flattenMap = /* @__PURE__ */ new Map();
|
|
1008
|
+
if (!opts?.noFlatten) {
|
|
1009
|
+
for (const f of fields) {
|
|
1010
|
+
const leaves = analyzeFlattenable(arr, f, "");
|
|
1011
|
+
if (leaves && leaves.length > 0) {
|
|
1012
|
+
flattenMap.set(f, leaves);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
const gtFields = /* @__PURE__ */ new Set();
|
|
1017
|
+
for (const f of fields) {
|
|
1018
|
+
if (!flattenMap.has(f) && f.includes(">")) {
|
|
1019
|
+
gtFields.add(f);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
const columns = [];
|
|
1023
|
+
for (const f of fields) {
|
|
1024
|
+
if (gtFields.has(f)) continue;
|
|
1025
|
+
const leaves = flattenMap.get(f);
|
|
1026
|
+
if (leaves) {
|
|
1027
|
+
for (const leaf of leaves) {
|
|
1028
|
+
columns.push({ headerName: formatKey(leaf.path), colType: "flat", field: f, keys: leaf.keys });
|
|
1029
|
+
}
|
|
1030
|
+
} else {
|
|
1031
|
+
columns.push({ headerName: formatKey(f), colType: "original", field: f, keys: [] });
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
if (columns.length === 0) {
|
|
1035
|
+
return encodeExpanded(headerPrefix, arr, depth, opts);
|
|
1036
|
+
}
|
|
923
1037
|
const inlineSchemas = /* @__PURE__ */ new Map();
|
|
924
1038
|
const sharedArrSchemas = /* @__PURE__ */ new Map();
|
|
925
1039
|
for (const f of fields) {
|
|
1040
|
+
if (flattenMap.has(f)) continue;
|
|
926
1041
|
const ifs = inlineSchemaFields(arr, f);
|
|
927
1042
|
if (ifs) inlineSchemas.set(f, ifs);
|
|
928
1043
|
const sas = sharedArraySchema(arr, f);
|
|
929
1044
|
if (sas) sharedArrSchemas.set(f, sas);
|
|
930
1045
|
}
|
|
931
|
-
const
|
|
932
|
-
let out = `${headerPrefix}[${arr.length}]{${
|
|
1046
|
+
const headerFields = columns.map((c) => c.headerName);
|
|
1047
|
+
let out = `${headerPrefix}[${arr.length}]{${headerFields.join(",")}}
|
|
933
1048
|
`;
|
|
934
1049
|
for (let i = 0; i < arr.length; i++) {
|
|
935
1050
|
const obj = arr[i];
|
|
936
1051
|
const cells = [];
|
|
937
1052
|
const attachments = [];
|
|
938
1053
|
let rowHasAttachment = false;
|
|
939
|
-
for (const
|
|
1054
|
+
for (const col of columns) {
|
|
1055
|
+
if (col.colType === "flat") {
|
|
1056
|
+
if (!(col.keys[0] in obj)) {
|
|
1057
|
+
cells.push("~");
|
|
1058
|
+
} else {
|
|
1059
|
+
const topVal = obj[col.keys[0]];
|
|
1060
|
+
if (topVal === null || topVal === void 0) {
|
|
1061
|
+
cells.push(topVal === null ? "-" : "~");
|
|
1062
|
+
} else {
|
|
1063
|
+
const resolved = resolveKeyChain(obj, col.keys);
|
|
1064
|
+
if (!resolved.exists) {
|
|
1065
|
+
cells.push("~");
|
|
1066
|
+
} else if (resolved.value === null || resolved.value === void 0) {
|
|
1067
|
+
cells.push("-");
|
|
1068
|
+
} else {
|
|
1069
|
+
cells.push(formatScalar(resolved.value, 124));
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
const f = col.field;
|
|
940
1076
|
if (!(f in obj)) {
|
|
941
1077
|
cells.push("~");
|
|
942
1078
|
continue;
|
|
@@ -965,6 +1101,13 @@ function encodeTabular(headerPrefix, arr, fields, depth) {
|
|
|
965
1101
|
cells.push(formatScalar(v, 124));
|
|
966
1102
|
}
|
|
967
1103
|
}
|
|
1104
|
+
for (const f of fields) {
|
|
1105
|
+
if (!gtFields.has(f)) continue;
|
|
1106
|
+
const obj2 = arr[i];
|
|
1107
|
+
if (!(f in obj2)) continue;
|
|
1108
|
+
rowHasAttachment = true;
|
|
1109
|
+
attachments.push({ name: f, value: obj2[f], inline: false });
|
|
1110
|
+
}
|
|
968
1111
|
const row = cells.join("|");
|
|
969
1112
|
if (rowHasAttachment) {
|
|
970
1113
|
out += `${prefix}@${i} ${row}
|
|
@@ -986,20 +1129,28 @@ function encodeTabular(headerPrefix, arr, fields, depth) {
|
|
|
986
1129
|
} else if (Array.isArray(att.value)) {
|
|
987
1130
|
const sas = sharedArrSchemas.get(att.name);
|
|
988
1131
|
if (sas && i > 0) {
|
|
989
|
-
out += encodeAttachmentArrayShared(prefix, fk, att.value, depth + 2, sas);
|
|
1132
|
+
out += encodeAttachmentArrayShared(prefix, fk, att.value, depth + 2, sas, opts);
|
|
990
1133
|
} else {
|
|
991
|
-
out += encodeAttachmentArray(prefix, fk, att.value, depth + 2);
|
|
1134
|
+
out += encodeAttachmentArray(prefix, fk, att.value, depth + 2, opts);
|
|
992
1135
|
}
|
|
993
|
-
} else {
|
|
1136
|
+
} else if (typeof att.value === "object" && att.value !== null) {
|
|
994
1137
|
out += `${prefix}.${fk} {}
|
|
995
1138
|
`;
|
|
996
|
-
out += encodeObject(att.value, depth + 2);
|
|
1139
|
+
out += encodeObject(att.value, depth + 2, opts);
|
|
1140
|
+
} else {
|
|
1141
|
+
if (att.value === null || att.value === void 0) {
|
|
1142
|
+
out += `${prefix}.${fk} =-
|
|
1143
|
+
`;
|
|
1144
|
+
} else {
|
|
1145
|
+
out += `${prefix}.${fk} =${formatScalar(att.value, 0)}
|
|
1146
|
+
`;
|
|
1147
|
+
}
|
|
997
1148
|
}
|
|
998
1149
|
}
|
|
999
1150
|
}
|
|
1000
1151
|
return out;
|
|
1001
1152
|
}
|
|
1002
|
-
function encodeAttachmentArray(attPrefix, fk, arr, depth) {
|
|
1153
|
+
function encodeAttachmentArray(attPrefix, fk, arr, depth, opts) {
|
|
1003
1154
|
if (arr.length === 0) return `${attPrefix}.${fk} [0]
|
|
1004
1155
|
`;
|
|
1005
1156
|
if (allPrimitives(arr)) {
|
|
@@ -1008,10 +1159,10 @@ function encodeAttachmentArray(attPrefix, fk, arr, depth) {
|
|
|
1008
1159
|
`;
|
|
1009
1160
|
}
|
|
1010
1161
|
const fields = tabularFields(arr);
|
|
1011
|
-
if (fields) return encodeTabular(`${attPrefix}.${fk} `, arr, fields, depth);
|
|
1012
|
-
return encodeExpanded(`${attPrefix}.${fk} `, arr, depth);
|
|
1162
|
+
if (fields) return encodeTabular(`${attPrefix}.${fk} `, arr, fields, depth, opts);
|
|
1163
|
+
return encodeExpanded(`${attPrefix}.${fk} `, arr, depth, opts);
|
|
1013
1164
|
}
|
|
1014
|
-
function encodeAttachmentArrayShared(attPrefix, fk, arr, depth, sharedFields) {
|
|
1165
|
+
function encodeAttachmentArrayShared(attPrefix, fk, arr, depth, sharedFields, opts) {
|
|
1015
1166
|
if (arr.length === 0) return `${attPrefix}.${fk} [0]
|
|
1016
1167
|
`;
|
|
1017
1168
|
if (allPrimitives(arr)) {
|
|
@@ -1036,20 +1187,20 @@ function encodeAttachmentArrayShared(attPrefix, fk, arr, depth, sharedFields) {
|
|
|
1036
1187
|
}
|
|
1037
1188
|
return out;
|
|
1038
1189
|
}
|
|
1039
|
-
return encodeAttachmentArray(attPrefix, fk, arr, depth);
|
|
1190
|
+
return encodeAttachmentArray(attPrefix, fk, arr, depth, opts);
|
|
1040
1191
|
}
|
|
1041
|
-
function encodeExpanded(headerPrefix, arr, depth) {
|
|
1192
|
+
function encodeExpanded(headerPrefix, arr, depth, opts) {
|
|
1042
1193
|
const prefix = indent(depth);
|
|
1043
1194
|
let out = `${headerPrefix}[${arr.length}]
|
|
1044
1195
|
`;
|
|
1045
1196
|
for (let i = 0; i < arr.length; i++) {
|
|
1046
1197
|
const item = arr[i];
|
|
1047
1198
|
if (Array.isArray(item)) {
|
|
1048
|
-
out += encodeExpandedArrayItem(prefix, i, item, depth);
|
|
1199
|
+
out += encodeExpandedArrayItem(prefix, i, item, depth, opts);
|
|
1049
1200
|
} else if (typeof item === "object" && item !== null) {
|
|
1050
1201
|
out += `${prefix}@${i} {}
|
|
1051
1202
|
`;
|
|
1052
|
-
out += encodeObject(item, depth + 1);
|
|
1203
|
+
out += encodeObject(item, depth + 1, opts);
|
|
1053
1204
|
} else {
|
|
1054
1205
|
out += `${prefix}@${i} =${formatScalar(item, 0)}
|
|
1055
1206
|
`;
|
|
@@ -1057,7 +1208,7 @@ function encodeExpanded(headerPrefix, arr, depth) {
|
|
|
1057
1208
|
}
|
|
1058
1209
|
return out;
|
|
1059
1210
|
}
|
|
1060
|
-
function encodeExpandedArrayItem(prefix, idx, arr, depth) {
|
|
1211
|
+
function encodeExpandedArrayItem(prefix, idx, arr, depth, opts) {
|
|
1061
1212
|
if (arr.length === 0) return `${prefix}@${idx} [0]
|
|
1062
1213
|
`;
|
|
1063
1214
|
if (allPrimitives(arr)) {
|
|
@@ -1066,8 +1217,8 @@ function encodeExpandedArrayItem(prefix, idx, arr, depth) {
|
|
|
1066
1217
|
`;
|
|
1067
1218
|
}
|
|
1068
1219
|
const fields = tabularFields(arr);
|
|
1069
|
-
if (fields) return encodeTabular(`${prefix}@${idx} `, arr, fields, depth + 1);
|
|
1070
|
-
return encodeExpanded(`${prefix}@${idx} `, arr, depth + 1);
|
|
1220
|
+
if (fields) return encodeTabular(`${prefix}@${idx} `, arr, fields, depth + 1, opts);
|
|
1221
|
+
return encodeExpanded(`${prefix}@${idx} `, arr, depth + 1, opts);
|
|
1071
1222
|
}
|
|
1072
1223
|
function allPrimitives(arr) {
|
|
1073
1224
|
return arr.every((v) => typeof v !== "object" || v === null);
|
|
@@ -1295,10 +1446,59 @@ function findClosingBrace2(s) {
|
|
|
1295
1446
|
}
|
|
1296
1447
|
return -1;
|
|
1297
1448
|
}
|
|
1449
|
+
function unflattenPaths(pathColumns, flatValues, flatAbsent) {
|
|
1450
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1451
|
+
const groupOrder = [];
|
|
1452
|
+
for (const [fieldName, paths] of pathColumns) {
|
|
1453
|
+
if (paths.length === 0) continue;
|
|
1454
|
+
const top = paths[0];
|
|
1455
|
+
if (!groups.has(top)) {
|
|
1456
|
+
groups.set(top, []);
|
|
1457
|
+
groupOrder.push(top);
|
|
1458
|
+
}
|
|
1459
|
+
groups.get(top).push(fieldName);
|
|
1460
|
+
}
|
|
1461
|
+
const result = {};
|
|
1462
|
+
for (const top of groupOrder) {
|
|
1463
|
+
const fieldNames = groups.get(top);
|
|
1464
|
+
const allAbsent = fieldNames.every((f) => flatAbsent.has(f));
|
|
1465
|
+
const allNull = fieldNames.every((f) => {
|
|
1466
|
+
if (flatAbsent.has(f)) return false;
|
|
1467
|
+
const val = flatValues.get(f);
|
|
1468
|
+
return val === null;
|
|
1469
|
+
});
|
|
1470
|
+
if (allAbsent) continue;
|
|
1471
|
+
if (allNull) {
|
|
1472
|
+
result[top] = null;
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
for (const fieldName of fieldNames) {
|
|
1476
|
+
if (flatAbsent.has(fieldName)) continue;
|
|
1477
|
+
const paths = pathColumns.get(fieldName);
|
|
1478
|
+
const val = flatValues.has(fieldName) ? flatValues.get(fieldName) : null;
|
|
1479
|
+
let current = result;
|
|
1480
|
+
for (let k = 0; k < paths.length - 1; k++) {
|
|
1481
|
+
if (!(paths[k] in current)) current[paths[k]] = {};
|
|
1482
|
+
current = current[paths[k]];
|
|
1483
|
+
}
|
|
1484
|
+
current[paths[paths.length - 1]] = val;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
return result;
|
|
1488
|
+
}
|
|
1298
1489
|
function parseTabularBody(lines, start, depth, fields, expectedCount) {
|
|
1299
1490
|
const ind = " ".repeat(depth);
|
|
1300
1491
|
const rows = [];
|
|
1301
1492
|
let i = start;
|
|
1493
|
+
const pathColumnMap = /* @__PURE__ */ new Map();
|
|
1494
|
+
for (const f of fields) {
|
|
1495
|
+
if (f.includes(">")) {
|
|
1496
|
+
const parts = f.split(">");
|
|
1497
|
+
if (parts.every((p) => p.length > 0)) {
|
|
1498
|
+
pathColumnMap.set(f, parts);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1302
1502
|
const inlineSchemas = /* @__PURE__ */ new Map();
|
|
1303
1503
|
const sharedArraySchemas = /* @__PURE__ */ new Map();
|
|
1304
1504
|
while (i < lines.length) {
|
|
@@ -1330,8 +1530,19 @@ function parseTabularBody(lines, start, depth, fields, expectedCount) {
|
|
|
1330
1530
|
const inlineAttFields = [];
|
|
1331
1531
|
const inlineAttOrder = [];
|
|
1332
1532
|
const missingFields = /* @__PURE__ */ new Set();
|
|
1533
|
+
const flatValues = /* @__PURE__ */ new Map();
|
|
1534
|
+
const flatAbsent = /* @__PURE__ */ new Set();
|
|
1333
1535
|
for (let j = 0; j < fields.length; j++) {
|
|
1334
1536
|
const cellVal = vals[j];
|
|
1537
|
+
if (pathColumnMap.has(fields[j])) {
|
|
1538
|
+
const parsed2 = parseScalar(cellVal, true);
|
|
1539
|
+
if (parsed2 === MISSING) {
|
|
1540
|
+
flatAbsent.add(fields[j]);
|
|
1541
|
+
} else {
|
|
1542
|
+
flatValues.set(fields[j], parsed2);
|
|
1543
|
+
}
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1335
1546
|
if (cellVal.startsWith("^{") && cellVal.endsWith("}")) {
|
|
1336
1547
|
const schemaStr = cellVal.slice(1);
|
|
1337
1548
|
const ifs = splitFieldDecl(schemaStr);
|
|
@@ -1366,9 +1577,9 @@ function parseTabularBody(lines, start, depth, fields, expectedCount) {
|
|
|
1366
1577
|
i++;
|
|
1367
1578
|
const allAttFields = [...traditionalAttFields, ...inlineAttFields];
|
|
1368
1579
|
const attachmentValues = /* @__PURE__ */ new Map();
|
|
1369
|
-
if (rowHasID
|
|
1580
|
+
if (rowHasID) {
|
|
1370
1581
|
let inlineIdx = 0;
|
|
1371
|
-
while (i < lines.length
|
|
1582
|
+
while (i < lines.length) {
|
|
1372
1583
|
const aLine = lines[i];
|
|
1373
1584
|
let aContent = null;
|
|
1374
1585
|
if (depth === 0 || aLine.startsWith(ind)) {
|
|
@@ -1467,11 +1678,13 @@ function parseTabularBody(lines, start, depth, fields, expectedCount) {
|
|
|
1467
1678
|
continue;
|
|
1468
1679
|
}
|
|
1469
1680
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1681
|
+
for (const [k, v] of attachmentValues) {
|
|
1682
|
+
if (!(k in row)) row[k] = v;
|
|
1683
|
+
}
|
|
1684
|
+
if (pathColumnMap.size > 0) {
|
|
1685
|
+
const nested = unflattenPaths(pathColumnMap, flatValues, flatAbsent);
|
|
1686
|
+
for (const [k, v] of Object.entries(nested)) {
|
|
1687
|
+
row[k] = v;
|
|
1475
1688
|
}
|
|
1476
1689
|
}
|
|
1477
1690
|
rows.push(row);
|
|
@@ -1549,6 +1762,12 @@ function parseAttachment(lines, lineIdx, rest, depth, sharedSchemas) {
|
|
|
1549
1762
|
const [arr, consumed] = parseArrayFromHeader(lines, lineIdx, depth, afterName);
|
|
1550
1763
|
return [name, arr, consumed, null];
|
|
1551
1764
|
}
|
|
1765
|
+
if (afterName.startsWith("=")) {
|
|
1766
|
+
const valStr = afterName.slice(1);
|
|
1767
|
+
const parsed = parseScalar(valStr, true);
|
|
1768
|
+
if (parsed === MISSING) return [name, null, 1, null];
|
|
1769
|
+
return [name, parsed, 1, null];
|
|
1770
|
+
}
|
|
1552
1771
|
throw new Error(`invalid attachment form: ${afterName}`);
|
|
1553
1772
|
}
|
|
1554
1773
|
function parseExpandedBody(lines, start, depth) {
|
package/dist/cli.js
CHANGED
|
File without changes
|
package/dist/decode_generic.js
CHANGED
|
@@ -271,10 +271,68 @@ function findClosingBrace(s) {
|
|
|
271
271
|
}
|
|
272
272
|
return -1;
|
|
273
273
|
}
|
|
274
|
+
function unflattenPaths(pathColumns, flatValues, flatAbsent) {
|
|
275
|
+
// Group by top-level parent.
|
|
276
|
+
const groups = new Map();
|
|
277
|
+
const groupOrder = [];
|
|
278
|
+
for (const [fieldName, paths] of pathColumns) {
|
|
279
|
+
if (paths.length === 0)
|
|
280
|
+
continue;
|
|
281
|
+
const top = paths[0];
|
|
282
|
+
if (!groups.has(top)) {
|
|
283
|
+
groups.set(top, []);
|
|
284
|
+
groupOrder.push(top);
|
|
285
|
+
}
|
|
286
|
+
groups.get(top).push(fieldName);
|
|
287
|
+
}
|
|
288
|
+
const result = {};
|
|
289
|
+
for (const top of groupOrder) {
|
|
290
|
+
const fieldNames = groups.get(top);
|
|
291
|
+
const allAbsent = fieldNames.every(f => flatAbsent.has(f));
|
|
292
|
+
const allNull = fieldNames.every(f => {
|
|
293
|
+
if (flatAbsent.has(f))
|
|
294
|
+
return false;
|
|
295
|
+
const val = flatValues.get(f);
|
|
296
|
+
return val === null;
|
|
297
|
+
});
|
|
298
|
+
if (allAbsent)
|
|
299
|
+
continue;
|
|
300
|
+
if (allNull) {
|
|
301
|
+
result[top] = null;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
for (const fieldName of fieldNames) {
|
|
305
|
+
if (flatAbsent.has(fieldName))
|
|
306
|
+
continue;
|
|
307
|
+
const paths = pathColumns.get(fieldName);
|
|
308
|
+
const val = flatValues.has(fieldName) ? flatValues.get(fieldName) : null;
|
|
309
|
+
let current = result;
|
|
310
|
+
for (let k = 0; k < paths.length - 1; k++) {
|
|
311
|
+
if (!(paths[k] in current))
|
|
312
|
+
current[paths[k]] = {};
|
|
313
|
+
current = current[paths[k]];
|
|
314
|
+
}
|
|
315
|
+
current[paths[paths.length - 1]] = val;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
274
320
|
function parseTabularBody(lines, start, depth, fields, expectedCount) {
|
|
275
321
|
const ind = ' '.repeat(depth);
|
|
276
322
|
const rows = [];
|
|
277
323
|
let i = start;
|
|
324
|
+
// Detect path columns: fields containing ">".
|
|
325
|
+
const pathColumnMap = new Map();
|
|
326
|
+
for (const f of fields) {
|
|
327
|
+
if (f.includes('>')) {
|
|
328
|
+
const parts = f.split('>');
|
|
329
|
+
// Only treat as a path column if all segments are non-empty.
|
|
330
|
+
// A literal key like ">" would split into ["", ""].
|
|
331
|
+
if (parts.every(p => p.length > 0)) {
|
|
332
|
+
pathColumnMap.set(f, parts);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
278
336
|
// Track inline schemas and shared array schemas.
|
|
279
337
|
const inlineSchemas = new Map();
|
|
280
338
|
const sharedArraySchemas = new Map();
|
|
@@ -313,8 +371,22 @@ function parseTabularBody(lines, start, depth, fields, expectedCount) {
|
|
|
313
371
|
const inlineAttFields = [];
|
|
314
372
|
const inlineAttOrder = [];
|
|
315
373
|
const missingFields = new Set();
|
|
374
|
+
// Collect path column values for unflattening.
|
|
375
|
+
const flatValues = new Map();
|
|
376
|
+
const flatAbsent = new Set();
|
|
316
377
|
for (let j = 0; j < fields.length; j++) {
|
|
317
378
|
const cellVal = vals[j];
|
|
379
|
+
// Path columns: store values for later unflattening.
|
|
380
|
+
if (pathColumnMap.has(fields[j])) {
|
|
381
|
+
const parsed = parseScalar(cellVal, true);
|
|
382
|
+
if (parsed === MISSING) {
|
|
383
|
+
flatAbsent.add(fields[j]);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
flatValues.set(fields[j], parsed);
|
|
387
|
+
}
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
318
390
|
// Check for ^{fields} inline schema declaration.
|
|
319
391
|
if (cellVal.startsWith('^{') && cellVal.endsWith('}')) {
|
|
320
392
|
const schemaStr = cellVal.slice(1);
|
|
@@ -354,9 +426,9 @@ function parseTabularBody(lines, start, depth, fields, expectedCount) {
|
|
|
354
426
|
// Parse attachments in line order.
|
|
355
427
|
const allAttFields = [...traditionalAttFields, ...inlineAttFields];
|
|
356
428
|
const attachmentValues = new Map();
|
|
357
|
-
if (rowHasID
|
|
429
|
+
if (rowHasID) {
|
|
358
430
|
let inlineIdx = 0;
|
|
359
|
-
while (i < lines.length
|
|
431
|
+
while (i < lines.length) {
|
|
360
432
|
const aLine = lines[i];
|
|
361
433
|
let aContent = null;
|
|
362
434
|
if (depth === 0 || aLine.startsWith(ind)) {
|
|
@@ -476,12 +548,16 @@ function parseTabularBody(lines, start, depth, fields, expectedCount) {
|
|
|
476
548
|
continue;
|
|
477
549
|
}
|
|
478
550
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
551
|
+
// Also add any orphan attachment values (fields excluded from column list, e.g. ">" fields).
|
|
552
|
+
for (const [k, v] of attachmentValues) {
|
|
553
|
+
if (!(k in row))
|
|
554
|
+
row[k] = v;
|
|
555
|
+
}
|
|
556
|
+
// Unflatten path columns into nested objects.
|
|
557
|
+
if (pathColumnMap.size > 0) {
|
|
558
|
+
const nested = unflattenPaths(pathColumnMap, flatValues, flatAbsent);
|
|
559
|
+
for (const [k, v] of Object.entries(nested)) {
|
|
560
|
+
row[k] = v;
|
|
485
561
|
}
|
|
486
562
|
}
|
|
487
563
|
rows.push(row);
|
|
@@ -574,6 +650,14 @@ function parseAttachment(lines, lineIdx, rest, depth, sharedSchemas) {
|
|
|
574
650
|
const [arr, consumed] = parseArrayFromHeader(lines, lineIdx, depth, afterName);
|
|
575
651
|
return [name, arr, consumed, null];
|
|
576
652
|
}
|
|
653
|
+
// Scalar: =value (field names containing ">" excluded from tabular columns).
|
|
654
|
+
if (afterName.startsWith('=')) {
|
|
655
|
+
const valStr = afterName.slice(1);
|
|
656
|
+
const parsed = parseScalar(valStr, true);
|
|
657
|
+
if (parsed === MISSING)
|
|
658
|
+
return [name, null, 1, null];
|
|
659
|
+
return [name, parsed, 1, null];
|
|
660
|
+
}
|
|
577
661
|
throw new Error(`invalid attachment form: ${afterName}`);
|
|
578
662
|
}
|
|
579
663
|
function parseExpandedBody(lines, start, depth) {
|