@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.
Files changed (61) hide show
  1. package/README.md +6 -6
  2. package/dist/cjs/index.cjs +259 -40
  3. package/dist/cli.js +0 -0
  4. package/dist/decode_generic.js +92 -8
  5. package/dist/decode_generic.js.map +1 -1
  6. package/dist/generic.d.ts +9 -1
  7. package/dist/generic.d.ts.map +1 -1
  8. package/dist/generic.js +231 -35
  9. package/dist/generic.js.map +1 -1
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js.map +1 -1
  13. package/package.json +2 -2
  14. package/src/decode_generic.ts +97 -7
  15. package/src/generic.ts +232 -35
  16. package/src/index.ts +1 -1
  17. package/dist/cjs/browser.d.ts +0 -9
  18. package/dist/cjs/browser.js +0 -25
  19. package/dist/cjs/browser.js.map +0 -1
  20. package/dist/cjs/cli.d.ts +0 -5
  21. package/dist/cjs/cli.js +0 -136
  22. package/dist/cjs/cli.js.map +0 -1
  23. package/dist/cjs/constants.d.ts +0 -8
  24. package/dist/cjs/constants.js +0 -46
  25. package/dist/cjs/constants.js.map +0 -1
  26. package/dist/cjs/decode.d.ts +0 -5
  27. package/dist/cjs/decode.js +0 -197
  28. package/dist/cjs/decode.js.map +0 -1
  29. package/dist/cjs/decode_generic.d.ts +0 -4
  30. package/dist/cjs/decode_generic.js +0 -678
  31. package/dist/cjs/decode_generic.js.map +0 -1
  32. package/dist/cjs/delta.d.ts +0 -15
  33. package/dist/cjs/delta.js +0 -73
  34. package/dist/cjs/delta.js.map +0 -1
  35. package/dist/cjs/encode.d.ts +0 -5
  36. package/dist/cjs/encode.js +0 -89
  37. package/dist/cjs/encode.js.map +0 -1
  38. package/dist/cjs/generic.d.ts +0 -1
  39. package/dist/cjs/generic.js +0 -332
  40. package/dist/cjs/generic.js.map +0 -1
  41. package/dist/cjs/index.d.ts +0 -11
  42. package/dist/cjs/index.js +0 -32
  43. package/dist/cjs/index.js.map +0 -1
  44. package/dist/cjs/packroot.d.ts +0 -13
  45. package/dist/cjs/packroot.js +0 -50
  46. package/dist/cjs/packroot.js.map +0 -1
  47. package/dist/cjs/scalar.d.ts +0 -26
  48. package/dist/cjs/scalar.js +0 -339
  49. package/dist/cjs/scalar.js.map +0 -1
  50. package/dist/cjs/session.d.ts +0 -30
  51. package/dist/cjs/session.js +0 -140
  52. package/dist/cjs/session.js.map +0 -1
  53. package/dist/cjs/stream.d.ts +0 -66
  54. package/dist/cjs/stream.js +0 -127
  55. package/dist/cjs/stream.js.map +0 -1
  56. package/dist/cjs/stream_generic.d.ts +0 -37
  57. package/dist/cjs/stream_generic.js +0 -87
  58. package/dist/cjs/stream_generic.js.map +0 -1
  59. package/dist/cjs/types.d.ts +0 -75
  60. package/dist/cjs/types.js +0 -3
  61. 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. 25.5% fewer tokens than TOON, 53% fewer than JSON across 15 datasets. 90.7% on structurally complex code graphs (vs TOON 68.5%, JSON 53.6%). 1,700+ LLM evaluations. Zero training.**
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
- 1,700+ LLM evaluations across 10 models, 3 providers, and 51 independent test runs.
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) | **90.7%** | 68.5% | 53.6% |
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 13/15 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)
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.1 Stable](https://github.com/blackwell-systems/gcf/blob/main/SPEC.md) with 157 conformance fixtures, 33,000,000,000+ lossless round-trips verified across 5 formats and 6 languages. All implementations at v2.1.0+ (Go v1.2.0). Cross-language 6x6 matrix verified.
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
 
@@ -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 encodeTabular(headerPrefix, arr, fields, depth) {
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 fmtFields = fields.map((f) => formatKey(f));
932
- let out = `${headerPrefix}[${arr.length}]{${fmtFields.join(",")}}
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 f of fields) {
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 && allAttFields.length > 0) {
1580
+ if (rowHasID) {
1370
1581
  let inlineIdx = 0;
1371
- while (i < lines.length && attachmentValues.size < allAttFields.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
- if (!rowHasID || allAttFields.length === 0) {
1471
- const attIndent = ind + " ";
1472
- if (i < lines.length && lines[i].startsWith(attIndent)) {
1473
- const peek = lines[i].slice(attIndent.length);
1474
- if (peek.startsWith(".")) throw new Error(`orphan_attachment: ${peek}`);
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
@@ -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 && allAttFields.length > 0) {
429
+ if (rowHasID) {
358
430
  let inlineIdx = 0;
359
- while (i < lines.length && attachmentValues.size < allAttFields.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
- if (!rowHasID || allAttFields.length === 0) {
480
- const attIndent = ind + ' ';
481
- if (i < lines.length && lines[i].startsWith(attIndent)) {
482
- const peek = lines[i].slice(attIndent.length);
483
- if (peek.startsWith('.'))
484
- throw new Error(`orphan_attachment: ${peek}`);
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) {