@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
@@ -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,WAAW,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5F,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,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,WAAW,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5F,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.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,WAAW,EAAE,MAAM,YAAY,CAAC;AACtD,sIAAsI;AACtI,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5F,OAAO,EAAE,aAAa,EAAyC,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,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,WAAW,EAAE,MAAM,YAAY,CAAC;AACtD,sIAAsI;AACtI,OAAO,EAAE,aAAa,EAAuB,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5F,OAAO,EAAE,aAAa,EAAyC,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackwell-systems/gcf",
3
- "version": "2.1.2",
4
- "description": "Drop-in JSON replacement for AI pipelines. 79% fewer tokens. 90.7% comprehension across 10 models. Zero dependencies.",
3
+ "version": "2.2.1",
4
+ "description": "The AI-native wire format for structured data. 50-92% fewer tokens than JSON. 100% comprehension on every frontier model. Zero dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -258,11 +258,73 @@ function findClosingBrace(s: string): number {
258
258
  return -1;
259
259
  }
260
260
 
261
+ function unflattenPaths(
262
+ pathColumns: Map<string, string[]>,
263
+ flatValues: Map<string, any>,
264
+ flatAbsent: Set<string>,
265
+ ): Record<string, any> {
266
+ // Group by top-level parent.
267
+ const groups = new Map<string, string[]>();
268
+ const groupOrder: string[] = [];
269
+ for (const [fieldName, paths] of pathColumns) {
270
+ if (paths.length === 0) continue;
271
+ const top = paths[0];
272
+ if (!groups.has(top)) {
273
+ groups.set(top, []);
274
+ groupOrder.push(top);
275
+ }
276
+ groups.get(top)!.push(fieldName);
277
+ }
278
+
279
+ const result: Record<string, any> = {};
280
+
281
+ for (const top of groupOrder) {
282
+ const fieldNames = groups.get(top)!;
283
+ const allAbsent = fieldNames.every(f => flatAbsent.has(f));
284
+ const allNull = fieldNames.every(f => {
285
+ if (flatAbsent.has(f)) return false;
286
+ const val = flatValues.get(f);
287
+ return val === null;
288
+ });
289
+
290
+ if (allAbsent) continue;
291
+ if (allNull) { result[top] = null; continue; }
292
+
293
+ for (const fieldName of fieldNames) {
294
+ if (flatAbsent.has(fieldName)) continue;
295
+ const paths = pathColumns.get(fieldName)!;
296
+ const val = flatValues.has(fieldName) ? flatValues.get(fieldName) : null;
297
+
298
+ let current = result;
299
+ for (let k = 0; k < paths.length - 1; k++) {
300
+ if (!(paths[k] in current)) current[paths[k]] = {};
301
+ current = current[paths[k]];
302
+ }
303
+ current[paths[paths.length - 1]] = val;
304
+ }
305
+ }
306
+
307
+ return result;
308
+ }
309
+
261
310
  function parseTabularBody(lines: string[], start: number, depth: number, fields: string[], expectedCount: number): [any[], number] {
262
311
  const ind = ' '.repeat(depth);
263
312
  const rows: any[] = [];
264
313
  let i = start;
265
314
 
315
+ // Detect path columns: fields containing ">".
316
+ const pathColumnMap = new Map<string, string[]>();
317
+ for (const f of fields) {
318
+ if (f.includes('>')) {
319
+ const parts = f.split('>');
320
+ // Only treat as a path column if all segments are non-empty.
321
+ // A literal key like ">" would split into ["", ""].
322
+ if (parts.every(p => p.length > 0)) {
323
+ pathColumnMap.set(f, parts);
324
+ }
325
+ }
326
+ }
327
+
266
328
  // Track inline schemas and shared array schemas.
267
329
  const inlineSchemas = new Map<string, string[]>();
268
330
  const sharedArraySchemas = new Map<string, string[]>();
@@ -303,9 +365,24 @@ function parseTabularBody(lines: string[], start: number, depth: number, fields:
303
365
  const inlineAttOrder: string[] = [];
304
366
  const missingFields = new Set<string>();
305
367
 
368
+ // Collect path column values for unflattening.
369
+ const flatValues = new Map<string, any>();
370
+ const flatAbsent = new Set<string>();
371
+
306
372
  for (let j = 0; j < fields.length; j++) {
307
373
  const cellVal = vals[j];
308
374
 
375
+ // Path columns: store values for later unflattening.
376
+ if (pathColumnMap.has(fields[j])) {
377
+ const parsed = parseScalar(cellVal, true);
378
+ if (parsed === MISSING) {
379
+ flatAbsent.add(fields[j]);
380
+ } else {
381
+ flatValues.set(fields[j], parsed);
382
+ }
383
+ continue;
384
+ }
385
+
309
386
  // Check for ^{fields} inline schema declaration.
310
387
  if (cellVal.startsWith('^{') && cellVal.endsWith('}')) {
311
388
  const schemaStr = cellVal.slice(1);
@@ -344,10 +421,10 @@ function parseTabularBody(lines: string[], start: number, depth: number, fields:
344
421
  const allAttFields = [...traditionalAttFields, ...inlineAttFields];
345
422
  const attachmentValues = new Map<string, any>();
346
423
 
347
- if (rowHasID && allAttFields.length > 0) {
424
+ if (rowHasID) {
348
425
  let inlineIdx = 0;
349
426
 
350
- while (i < lines.length && attachmentValues.size < allAttFields.length) {
427
+ while (i < lines.length) {
351
428
  const aLine = lines[i];
352
429
  let aContent: string | null = null;
353
430
  if (depth === 0 || aLine.startsWith(ind)) {
@@ -459,11 +536,16 @@ function parseTabularBody(lines: string[], start: number, depth: number, fields:
459
536
  if (attachmentValues.has(f)) { row[f] = attachmentValues.get(f); continue; }
460
537
  }
461
538
 
462
- if (!rowHasID || allAttFields.length === 0) {
463
- const attIndent = ind + ' ';
464
- if (i < lines.length && lines[i].startsWith(attIndent)) {
465
- const peek = lines[i].slice(attIndent.length);
466
- if (peek.startsWith('.')) throw new Error(`orphan_attachment: ${peek}`);
539
+ // Also add any orphan attachment values (fields excluded from column list, e.g. ">" fields).
540
+ for (const [k, v] of attachmentValues) {
541
+ if (!(k in row)) row[k] = v;
542
+ }
543
+
544
+ // Unflatten path columns into nested objects.
545
+ if (pathColumnMap.size > 0) {
546
+ const nested = unflattenPaths(pathColumnMap, flatValues, flatAbsent);
547
+ for (const [k, v] of Object.entries(nested)) {
548
+ row[k] = v;
467
549
  }
468
550
  }
469
551
 
@@ -553,6 +635,14 @@ function parseAttachment(lines: string[], lineIdx: number, rest: string, depth:
553
635
  return [name, arr, consumed, null];
554
636
  }
555
637
 
638
+ // Scalar: =value (field names containing ">" excluded from tabular columns).
639
+ if (afterName.startsWith('=')) {
640
+ const valStr = afterName.slice(1);
641
+ const parsed = parseScalar(valStr, true);
642
+ if (parsed === MISSING) return [name, null, 1, null];
643
+ return [name, parsed, 1, null];
644
+ }
645
+
556
646
  throw new Error(`invalid attachment form: ${afterName}`);
557
647
  }
558
648
 
package/src/generic.ts CHANGED
@@ -7,30 +7,39 @@ function indent(depth: number): string {
7
7
  return ' '.repeat(depth);
8
8
  }
9
9
 
10
- export function encodeGeneric(data: unknown): string {
10
+ /** Options for controlling generic encoding behavior. */
11
+ export interface GenericOptions {
12
+ /** When true, disables promotion of fixed-shape nested objects to path
13
+ * columns (e.g. "customer>name"). Nested objects use attachment syntax
14
+ * instead. Set when targeting open-weight models that show lower
15
+ * comprehension on flattened encoding. */
16
+ noFlatten?: boolean;
17
+ }
18
+
19
+ export function encodeGeneric(data: unknown, opts?: GenericOptions): string {
11
20
  let out = 'GCF profile=generic\n';
12
- out += encodeRootValue(data);
21
+ out += encodeRootValue(data, opts);
13
22
  return out;
14
23
  }
15
24
 
16
- function encodeRootValue(v: unknown): string {
25
+ function encodeRootValue(v: unknown, opts?: GenericOptions): string {
17
26
  if (v === null || v === undefined) return '=-\n';
18
- if (Array.isArray(v)) return encodeRootArray(v);
19
- if (typeof v === 'object') return encodeObject(v as Record<string, unknown>, 0);
27
+ if (Array.isArray(v)) return encodeRootArray(v, opts);
28
+ if (typeof v === 'object') return encodeObject(v as Record<string, unknown>, 0, opts);
20
29
  return `=${formatScalar(v, 0)}\n`;
21
30
  }
22
31
 
23
- function encodeObject(obj: Record<string, unknown>, depth: number): string {
32
+ function encodeObject(obj: Record<string, unknown>, depth: number, opts?: GenericOptions): string {
24
33
  const prefix = indent(depth);
25
34
  let out = '';
26
35
  for (const key of Object.keys(obj)) {
27
36
  const value = obj[key];
28
37
  const fk = formatKey(key);
29
38
  if (Array.isArray(value)) {
30
- out += encodeNamedArray(fk, value, depth);
39
+ out += encodeNamedArray(fk, value, depth, opts);
31
40
  } else if (typeof value === 'object' && value !== null) {
32
41
  out += `${prefix}## ${fk}\n`;
33
- out += encodeObject(value as Record<string, unknown>, depth + 1);
42
+ out += encodeObject(value as Record<string, unknown>, depth + 1, opts);
34
43
  } else {
35
44
  out += `${prefix}${fk}=${formatScalar(value, 0)}\n`;
36
45
  }
@@ -38,18 +47,18 @@ function encodeObject(obj: Record<string, unknown>, depth: number): string {
38
47
  return out;
39
48
  }
40
49
 
41
- function encodeRootArray(arr: unknown[]): string {
50
+ function encodeRootArray(arr: unknown[], opts?: GenericOptions): string {
42
51
  if (arr.length === 0) return '## [0]\n';
43
52
  if (allPrimitives(arr)) {
44
53
  const vals = arr.map(v => formatScalar(v, 0x2c));
45
54
  return `## [${arr.length}]: ${vals.join(',')}\n`;
46
55
  }
47
56
  const fields = tabularFields(arr);
48
- if (fields) return encodeTabular('## ', arr, fields, 0);
49
- return encodeExpanded('## ', arr, 0);
57
+ if (fields) return encodeTabular('## ', arr, fields, 0, opts);
58
+ return encodeExpanded('## ', arr, 0, opts);
50
59
  }
51
60
 
52
- function encodeNamedArray(name: string, arr: unknown[], depth: number): string {
61
+ function encodeNamedArray(name: string, arr: unknown[], depth: number, opts?: GenericOptions): string {
53
62
  const prefix = indent(depth);
54
63
  if (arr.length === 0) return `${prefix}## ${name} [0]\n`;
55
64
  if (allPrimitives(arr)) {
@@ -57,8 +66,8 @@ function encodeNamedArray(name: string, arr: unknown[], depth: number): string {
57
66
  return `${prefix}${name}[${arr.length}]: ${vals.join(',')}\n`;
58
67
  }
59
68
  const fields = tabularFields(arr);
60
- if (fields) return encodeTabular(`${prefix}## ${name} `, arr, fields, depth);
61
- return encodeExpanded(`${prefix}## ${name} `, arr, depth);
69
+ if (fields) return encodeTabular(`${prefix}## ${name} `, arr, fields, depth, opts);
70
+ return encodeExpanded(`${prefix}## ${name} `, arr, depth, opts);
62
71
  }
63
72
 
64
73
  function tabularFields(arr: unknown[]): string[] | null {
@@ -141,21 +150,169 @@ function sharedArraySchema(arr: unknown[], fieldName: string): string[] | null {
141
150
  return canonicalFields;
142
151
  }
143
152
 
144
- function encodeTabular(headerPrefix: string, arr: unknown[], fields: string[], depth: number): string {
153
+ // ── Nested object flattening (v3.2) ──────────────────────────────────────
154
+
155
+ interface FlatLeaf {
156
+ path: string; // ">" separated path (e.g. "customer>name")
157
+ keys: string[]; // key chain to traverse from row object
158
+ }
159
+
160
+ function analyzeFlattenable(arr: unknown[], fieldName: string, parentPath: string): FlatLeaf[] | null {
161
+ // Field names containing ">" cannot be flattened (would create ambiguous paths).
162
+ if (fieldName.includes('>')) return null;
163
+ let canonicalShape: Record<string, 'scalar' | 'nested'> | null = null;
164
+
165
+ for (const item of arr) {
166
+ const obj = item as Record<string, unknown>;
167
+ if (!(fieldName in obj) || obj[fieldName] === null || obj[fieldName] === undefined) continue;
168
+ const v = obj[fieldName];
169
+ if (typeof v !== 'object' || Array.isArray(v)) return null;
170
+
171
+ const keys = Object.keys(v as Record<string, unknown>);
172
+
173
+ if (!canonicalShape) {
174
+ canonicalShape = {};
175
+ for (const k of keys) {
176
+ if (k.includes('>')) return null;
177
+ const val = (v as Record<string, unknown>)[k];
178
+ if (val !== null && val !== undefined && typeof val === 'object' && !Array.isArray(val)) {
179
+ canonicalShape[k] = 'nested';
180
+ } else if (Array.isArray(val)) {
181
+ return null;
182
+ } else {
183
+ canonicalShape[k] = 'scalar';
184
+ }
185
+ }
186
+ } else {
187
+ if (keys.length !== Object.keys(canonicalShape).length) return null;
188
+ for (const k of keys) {
189
+ if (!(k in canonicalShape)) return null;
190
+ const val = (v as Record<string, unknown>)[k];
191
+ const expected = canonicalShape[k];
192
+ if (expected === 'scalar') {
193
+ if (val !== null && val !== undefined && typeof val === 'object') return null;
194
+ } else if (expected === 'nested') {
195
+ if (val !== null && val !== undefined) {
196
+ if (typeof val !== 'object' || Array.isArray(val)) return null;
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+
203
+ if (!canonicalShape) return null;
204
+
205
+ const currentPath = parentPath ? parentPath + '>' + fieldName : fieldName;
206
+ const parentKeys = parentPath ? [...parentPath.split('>'), fieldName] : [fieldName];
207
+
208
+ const leaves: FlatLeaf[] = [];
209
+ for (const k of Object.keys(canonicalShape)) {
210
+ if (canonicalShape[k] === 'scalar') {
211
+ leaves.push({ path: currentPath + '>' + k, keys: [...parentKeys, k] });
212
+ } else {
213
+ const subArr = arr.map(item => {
214
+ const obj = item as Record<string, unknown>;
215
+ if (!(fieldName in obj) || obj[fieldName] === null || obj[fieldName] === undefined) return {};
216
+ return obj[fieldName];
217
+ });
218
+ const subLeaves = analyzeFlattenable(subArr as unknown[], k, currentPath);
219
+ if (!subLeaves || subLeaves.length === 0) return null;
220
+ leaves.push(...subLeaves);
221
+ }
222
+ }
223
+
224
+ // Guard: reject if any row has non-null object with all-null leaves.
225
+ if (leaves.length > 0) {
226
+ for (const item of arr) {
227
+ const obj = item as Record<string, unknown>;
228
+ if (!(fieldName in obj) || obj[fieldName] === null || obj[fieldName] === undefined) continue;
229
+ const allNull = leaves.every(leaf => {
230
+ const val = resolveKeyChain(item, leaf.keys);
231
+ return val.exists && val.value === null;
232
+ });
233
+ if (allNull) return null;
234
+ }
235
+ }
236
+
237
+ return leaves;
238
+ }
239
+
240
+ function resolveKeyChain(item: unknown, keys: string[]): { value: unknown; exists: boolean } {
241
+ if (keys.length === 0) return { value: undefined, exists: false };
242
+ const obj = item as Record<string, unknown>;
243
+ if (typeof obj !== 'object' || obj === null) return { value: undefined, exists: false };
244
+ if (!(keys[0] in obj)) return { value: undefined, exists: false };
245
+ let current: unknown = obj[keys[0]];
246
+ if (current === null || current === undefined) return { value: current, exists: true };
247
+ for (let i = 1; i < keys.length; i++) {
248
+ if (typeof current !== 'object' || current === null) return { value: undefined, exists: false };
249
+ const c = current as Record<string, unknown>;
250
+ if (!(keys[i] in c)) return { value: undefined, exists: false };
251
+ current = c[keys[i]];
252
+ }
253
+ return { value: current, exists: true };
254
+ }
255
+
256
+ // ── End flattening helpers ───────────────────────────────────────────────
257
+
258
+ function encodeTabular(headerPrefix: string, arr: unknown[], fields: string[], depth: number, opts?: GenericOptions): string {
145
259
  const prefix = indent(depth);
146
260
 
147
- // Pre-compute inline schemas and shared array schemas.
261
+ // Phase 0: Analyze fields for flattening.
262
+ const flattenMap = new Map<string, FlatLeaf[]>();
263
+ if (!opts?.noFlatten) {
264
+ for (const f of fields) {
265
+ const leaves = analyzeFlattenable(arr, f, '');
266
+ if (leaves && leaves.length > 0) {
267
+ flattenMap.set(f, leaves);
268
+ }
269
+ }
270
+ }
271
+
272
+ // Fields whose names contain ">" must not appear as tabular columns
273
+ // because the decoder would interpret them as flattened path columns.
274
+ // Track them for per-row attachment emission (spec rule 7.4.6.1.4).
275
+ const gtFields = new Set<string>();
276
+ for (const f of fields) {
277
+ if (!flattenMap.has(f) && f.includes('>')) {
278
+ gtFields.add(f);
279
+ }
280
+ }
281
+
282
+ // Build expanded column list.
283
+ type ColType = 'flat' | 'original';
284
+ interface FlatColumn { headerName: string; colType: ColType; field: string; keys: string[]; }
285
+ const columns: FlatColumn[] = [];
286
+ for (const f of fields) {
287
+ if (gtFields.has(f)) continue;
288
+ const leaves = flattenMap.get(f);
289
+ if (leaves) {
290
+ for (const leaf of leaves) {
291
+ columns.push({ headerName: formatKey(leaf.path), colType: 'flat', field: f, keys: leaf.keys });
292
+ }
293
+ } else {
294
+ columns.push({ headerName: formatKey(f), colType: 'original', field: f, keys: [] });
295
+ }
296
+ }
297
+
298
+ // If all fields were excluded (all contain ">"), fall back to expanded.
299
+ if (columns.length === 0) {
300
+ return encodeExpanded(headerPrefix, arr, depth, opts);
301
+ }
302
+
303
+ // Pre-compute inline schemas and shared array schemas (skip flattened fields).
148
304
  const inlineSchemas = new Map<string, string[]>();
149
305
  const sharedArrSchemas = new Map<string, string[]>();
150
306
  for (const f of fields) {
307
+ if (flattenMap.has(f)) continue;
151
308
  const ifs = inlineSchemaFields(arr, f);
152
309
  if (ifs) inlineSchemas.set(f, ifs);
153
310
  const sas = sharedArraySchema(arr, f);
154
311
  if (sas) sharedArrSchemas.set(f, sas);
155
312
  }
156
313
 
157
- const fmtFields = fields.map(f => formatKey(f));
158
- let out = `${headerPrefix}[${arr.length}]{${fmtFields.join(',')}}\n`;
314
+ const headerFields = columns.map(c => c.headerName);
315
+ let out = `${headerPrefix}[${arr.length}]{${headerFields.join(',')}}\n`;
159
316
 
160
317
  for (let i = 0; i < arr.length; i++) {
161
318
  const obj = arr[i] as Record<string, unknown>;
@@ -163,14 +320,38 @@ function encodeTabular(headerPrefix: string, arr: unknown[], fields: string[], d
163
320
  const attachments: { name: string; value: unknown; inline: boolean; inlineFields?: string[] }[] = [];
164
321
  let rowHasAttachment = false;
165
322
 
166
- for (const f of fields) {
323
+ for (const col of columns) {
324
+ if (col.colType === 'flat') {
325
+ // Resolve value via key chain.
326
+ if (!(col.keys[0] in obj)) {
327
+ cells.push('~');
328
+ } else {
329
+ // Check if top-level field is null.
330
+ const topVal = obj[col.keys[0]];
331
+ if (topVal === null || topVal === undefined) {
332
+ cells.push(topVal === null ? '-' : '~');
333
+ } else {
334
+ const resolved = resolveKeyChain(obj, col.keys);
335
+ if (!resolved.exists) {
336
+ cells.push('~');
337
+ } else if (resolved.value === null || resolved.value === undefined) {
338
+ cells.push('-');
339
+ } else {
340
+ cells.push(formatScalar(resolved.value, 0x7c));
341
+ }
342
+ }
343
+ }
344
+ continue;
345
+ }
346
+
347
+ // Original (non-flattened) field.
348
+ const f = col.field;
167
349
  if (!(f in obj)) { cells.push('~'); continue; }
168
350
  const v = obj[f];
169
351
  if (v === null || v === undefined) { cells.push('-'); continue; }
170
352
  if (typeof v === 'object') {
171
353
  const ifs = inlineSchemas.get(f);
172
354
  if (ifs && !Array.isArray(v)) {
173
- // Inline schema: first row declares, subsequent use bare ^.
174
355
  if (i === 0) {
175
356
  const fmtIF = ifs.map(k => formatKey(k));
176
357
  cells.push(`^{${fmtIF.join(',')}}`);
@@ -188,6 +369,15 @@ function encodeTabular(headerPrefix: string, arr: unknown[], fields: string[], d
188
369
  }
189
370
  }
190
371
 
372
+ // Emit fields with ">" in their names as per-row attachments.
373
+ for (const f of fields) {
374
+ if (!gtFields.has(f)) continue;
375
+ const obj = arr[i] as Record<string, unknown>;
376
+ if (!(f in obj)) continue;
377
+ rowHasAttachment = true;
378
+ attachments.push({ name: f, value: obj[f], inline: false });
379
+ }
380
+
191
381
  const row = cells.join('|');
192
382
  if (rowHasAttachment) {
193
383
  out += `${prefix}@${i} ${row}\n`;
@@ -209,31 +399,38 @@ function encodeTabular(headerPrefix: string, arr: unknown[], fields: string[], d
209
399
  // Shared array schema: omit {fields} on subsequent rows.
210
400
  const sas = sharedArrSchemas.get(att.name);
211
401
  if (sas && i > 0) {
212
- out += encodeAttachmentArrayShared(prefix, fk, att.value as unknown[], depth + 2, sas);
402
+ out += encodeAttachmentArrayShared(prefix, fk, att.value as unknown[], depth + 2, sas, opts);
213
403
  } else {
214
- out += encodeAttachmentArray(prefix, fk, att.value as unknown[], depth + 2);
404
+ out += encodeAttachmentArray(prefix, fk, att.value as unknown[], depth + 2, opts);
215
405
  }
216
- } else {
406
+ } else if (typeof att.value === 'object' && att.value !== null) {
217
407
  out += `${prefix}.${fk} {}\n`;
218
- out += encodeObject(att.value as Record<string, unknown>, depth + 2);
408
+ out += encodeObject(att.value as Record<string, unknown>, depth + 2, opts);
409
+ } else {
410
+ // Scalar attachment (e.g. field names containing ">").
411
+ if (att.value === null || att.value === undefined) {
412
+ out += `${prefix}.${fk} =-\n`;
413
+ } else {
414
+ out += `${prefix}.${fk} =${formatScalar(att.value, 0)}\n`;
415
+ }
219
416
  }
220
417
  }
221
418
  }
222
419
  return out;
223
420
  }
224
421
 
225
- function encodeAttachmentArray(attPrefix: string, fk: string, arr: unknown[], depth: number): string {
422
+ function encodeAttachmentArray(attPrefix: string, fk: string, arr: unknown[], depth: number, opts?: GenericOptions): string {
226
423
  if (arr.length === 0) return `${attPrefix}.${fk} [0]\n`;
227
424
  if (allPrimitives(arr)) {
228
425
  const vals = arr.map(v => formatScalar(v, 0x2c));
229
426
  return `${attPrefix}.${fk} [${arr.length}]: ${vals.join(',')}\n`;
230
427
  }
231
428
  const fields = tabularFields(arr);
232
- if (fields) return encodeTabular(`${attPrefix}.${fk} `, arr, fields, depth);
233
- return encodeExpanded(`${attPrefix}.${fk} `, arr, depth);
429
+ if (fields) return encodeTabular(`${attPrefix}.${fk} `, arr, fields, depth, opts);
430
+ return encodeExpanded(`${attPrefix}.${fk} `, arr, depth, opts);
234
431
  }
235
432
 
236
- function encodeAttachmentArrayShared(attPrefix: string, fk: string, arr: unknown[], depth: number, sharedFields: string[]): string {
433
+ function encodeAttachmentArrayShared(attPrefix: string, fk: string, arr: unknown[], depth: number, sharedFields: string[], opts?: GenericOptions): string {
237
434
  if (arr.length === 0) return `${attPrefix}.${fk} [0]\n`;
238
435
  if (allPrimitives(arr)) {
239
436
  const vals = arr.map(v => formatScalar(v, 0x2c));
@@ -257,19 +454,19 @@ function encodeAttachmentArrayShared(attPrefix: string, fk: string, arr: unknown
257
454
  return out;
258
455
  }
259
456
  // Fields don't match: fall back to full encoding.
260
- return encodeAttachmentArray(attPrefix, fk, arr, depth);
457
+ return encodeAttachmentArray(attPrefix, fk, arr, depth, opts);
261
458
  }
262
459
 
263
- function encodeExpanded(headerPrefix: string, arr: unknown[], depth: number): string {
460
+ function encodeExpanded(headerPrefix: string, arr: unknown[], depth: number, opts?: GenericOptions): string {
264
461
  const prefix = indent(depth);
265
462
  let out = `${headerPrefix}[${arr.length}]\n`;
266
463
  for (let i = 0; i < arr.length; i++) {
267
464
  const item = arr[i];
268
465
  if (Array.isArray(item)) {
269
- out += encodeExpandedArrayItem(prefix, i, item, depth);
466
+ out += encodeExpandedArrayItem(prefix, i, item, depth, opts);
270
467
  } else if (typeof item === 'object' && item !== null) {
271
468
  out += `${prefix}@${i} {}\n`;
272
- out += encodeObject(item as Record<string, unknown>, depth + 1);
469
+ out += encodeObject(item as Record<string, unknown>, depth + 1, opts);
273
470
  } else {
274
471
  out += `${prefix}@${i} =${formatScalar(item, 0)}\n`;
275
472
  }
@@ -277,15 +474,15 @@ function encodeExpanded(headerPrefix: string, arr: unknown[], depth: number): st
277
474
  return out;
278
475
  }
279
476
 
280
- function encodeExpandedArrayItem(prefix: string, idx: number, arr: unknown[], depth: number): string {
477
+ function encodeExpandedArrayItem(prefix: string, idx: number, arr: unknown[], depth: number, opts?: GenericOptions): string {
281
478
  if (arr.length === 0) return `${prefix}@${idx} [0]\n`;
282
479
  if (allPrimitives(arr)) {
283
480
  const vals = arr.map(v => formatScalar(v, 0x2c));
284
481
  return `${prefix}@${idx} [${arr.length}]: ${vals.join(',')}\n`;
285
482
  }
286
483
  const fields = tabularFields(arr);
287
- if (fields) return encodeTabular(`${prefix}@${idx} `, arr, fields, depth + 1);
288
- return encodeExpanded(`${prefix}@${idx} `, arr, depth + 1);
484
+ if (fields) return encodeTabular(`${prefix}@${idx} `, arr, fields, depth + 1, opts);
485
+ return encodeExpanded(`${prefix}@${idx} `, arr, depth + 1, opts);
289
486
  }
290
487
 
291
488
  function allPrimitives(arr: unknown[]): boolean {
package/src/index.ts CHANGED
@@ -5,7 +5,7 @@ export { decode } from './decode.js';
5
5
  export { Session, encodeWithSession } from './session.js';
6
6
  export { encodeDelta, verifyDelta } from './delta.js';
7
7
  // packRoot is Node-only (uses crypto.createHash). Import directly: import { packRoot } from '@blackwell-systems/gcf/dist/packroot.js'
8
- export { encodeGeneric } from './generic.js';
8
+ export { encodeGeneric, type GenericOptions } from './generic.js';
9
9
  export { decodeGeneric } from './decode_generic.js';
10
10
  export { formatScalar, formatKey, parseScalar, needsQuote, quoteString } from './scalar.js';
11
11
  export { StreamEncoder, type StreamWriter, type StreamOptions } from './stream.js';
@@ -1,9 +0,0 @@
1
- export type { Symbol, Edge, Payload, DeltaPayload, Components } from './types.js';
2
- export { KIND_ABBREV, KIND_EXPAND } from './constants.js';
3
- export { encode } from './encode.js';
4
- export { decode } from './decode.js';
5
- export { encodeGeneric } from './generic.js';
6
- export { decodeGeneric } from './decode_generic.js';
7
- export { formatScalar, formatKey, parseScalar, needsQuote, quoteString } from './scalar.js';
8
- export { StreamEncoder, type StreamWriter, type StreamOptions } from './stream.js';
9
- export { GenericStreamEncoder } from './stream_generic.js';
@@ -1,25 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GenericStreamEncoder = exports.StreamEncoder = exports.quoteString = exports.needsQuote = exports.parseScalar = exports.formatKey = exports.formatScalar = exports.decodeGeneric = exports.encodeGeneric = exports.decode = exports.encode = exports.KIND_EXPAND = exports.KIND_ABBREV = void 0;
4
- var constants_js_1 = require("./constants.js");
5
- Object.defineProperty(exports, "KIND_ABBREV", { enumerable: true, get: function () { return constants_js_1.KIND_ABBREV; } });
6
- Object.defineProperty(exports, "KIND_EXPAND", { enumerable: true, get: function () { return constants_js_1.KIND_EXPAND; } });
7
- var encode_js_1 = require("./encode.js");
8
- Object.defineProperty(exports, "encode", { enumerable: true, get: function () { return encode_js_1.encode; } });
9
- var decode_js_1 = require("./decode.js");
10
- Object.defineProperty(exports, "decode", { enumerable: true, get: function () { return decode_js_1.decode; } });
11
- var generic_js_1 = require("./generic.js");
12
- Object.defineProperty(exports, "encodeGeneric", { enumerable: true, get: function () { return generic_js_1.encodeGeneric; } });
13
- var decode_generic_js_1 = require("./decode_generic.js");
14
- Object.defineProperty(exports, "decodeGeneric", { enumerable: true, get: function () { return decode_generic_js_1.decodeGeneric; } });
15
- var scalar_js_1 = require("./scalar.js");
16
- Object.defineProperty(exports, "formatScalar", { enumerable: true, get: function () { return scalar_js_1.formatScalar; } });
17
- Object.defineProperty(exports, "formatKey", { enumerable: true, get: function () { return scalar_js_1.formatKey; } });
18
- Object.defineProperty(exports, "parseScalar", { enumerable: true, get: function () { return scalar_js_1.parseScalar; } });
19
- Object.defineProperty(exports, "needsQuote", { enumerable: true, get: function () { return scalar_js_1.needsQuote; } });
20
- Object.defineProperty(exports, "quoteString", { enumerable: true, get: function () { return scalar_js_1.quoteString; } });
21
- var stream_js_1 = require("./stream.js");
22
- Object.defineProperty(exports, "StreamEncoder", { enumerable: true, get: function () { return stream_js_1.StreamEncoder; } });
23
- var stream_generic_js_1 = require("./stream_generic.js");
24
- Object.defineProperty(exports, "GenericStreamEncoder", { enumerable: true, get: function () { return stream_generic_js_1.GenericStreamEncoder; } });
25
- //# sourceMappingURL=browser.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/browser.ts"],"names":[],"mappings":";;;AAEA,+CAA0D;AAAjD,2GAAA,WAAW,OAAA;AAAE,2GAAA,WAAW,OAAA;AACjC,yCAAqC;AAA5B,mGAAA,MAAM,OAAA;AACf,yCAAqC;AAA5B,mGAAA,MAAM,OAAA;AACf,2CAA6C;AAApC,2GAAA,aAAa,OAAA;AACtB,yDAAoD;AAA3C,kHAAA,aAAa,OAAA;AACtB,yCAA4F;AAAnF,yGAAA,YAAY,OAAA;AAAE,sGAAA,SAAS,OAAA;AAAE,wGAAA,WAAW,OAAA;AAAE,uGAAA,UAAU,OAAA;AAAE,wGAAA,WAAW,OAAA;AACtE,yCAAmF;AAA1E,0GAAA,aAAa,OAAA;AACtB,yDAA2D;AAAlD,yHAAA,oBAAoB,OAAA"}
package/dist/cjs/cli.d.ts DELETED
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * GCF command-line interface: encode, decode, stats.
4
- */
5
- export {};