@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/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,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,
|
|
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
|
|
4
|
-
"description": "
|
|
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",
|
package/src/decode_generic.ts
CHANGED
|
@@ -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
|
|
424
|
+
if (rowHasID) {
|
|
348
425
|
let inlineIdx = 0;
|
|
349
426
|
|
|
350
|
-
while (i < lines.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
|
-
|
|
463
|
-
|
|
464
|
-
if (
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
158
|
-
let out = `${headerPrefix}[${arr.length}]{${
|
|
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
|
|
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';
|
package/dist/cjs/browser.d.ts
DELETED
|
@@ -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';
|
package/dist/cjs/browser.js
DELETED
|
@@ -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
|
package/dist/cjs/browser.js.map
DELETED
|
@@ -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"}
|