@yeseh/cortex-cli 0.6.7 → 0.6.9
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/dist/program.js +1538 -5
- package/dist/program.js.map +32 -3
- package/dist/run.d.ts +0 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +3 -4
- package/dist/run.js.map +3 -3
- package/package.json +4 -6
- package/dist/chunk-tgrm2cc9.js +0 -1543
- package/dist/chunk-tgrm2cc9.js.map +0 -38
- package/src/category/commands/create.spec.ts +0 -139
- package/src/category/commands/create.ts +0 -119
- package/src/category/index.ts +0 -24
- package/src/commands/init.spec.ts +0 -203
- package/src/commands/init.ts +0 -301
- package/src/context.spec.ts +0 -60
- package/src/context.ts +0 -170
- package/src/errors.spec.ts +0 -264
- package/src/errors.ts +0 -105
- package/src/memory/commands/add.spec.ts +0 -169
- package/src/memory/commands/add.ts +0 -158
- package/src/memory/commands/definitions.spec.ts +0 -80
- package/src/memory/commands/list.spec.ts +0 -123
- package/src/memory/commands/list.ts +0 -269
- package/src/memory/commands/move.spec.ts +0 -85
- package/src/memory/commands/move.ts +0 -119
- package/src/memory/commands/remove.spec.ts +0 -79
- package/src/memory/commands/remove.ts +0 -108
- package/src/memory/commands/show.spec.ts +0 -71
- package/src/memory/commands/show.ts +0 -165
- package/src/memory/commands/test-helpers.spec.ts +0 -127
- package/src/memory/commands/update.spec.ts +0 -86
- package/src/memory/commands/update.ts +0 -230
- package/src/memory/index.spec.ts +0 -59
- package/src/memory/index.ts +0 -44
- package/src/memory/parsing.spec.ts +0 -105
- package/src/memory/parsing.ts +0 -22
- package/src/observability.spec.ts +0 -126
- package/src/observability.ts +0 -82
- package/src/output.spec.ts +0 -835
- package/src/output.ts +0 -119
- package/src/program.spec.ts +0 -46
- package/src/program.ts +0 -75
- package/src/run.spec.ts +0 -31
- package/src/run.ts +0 -9
- package/src/store/commands/add.spec.ts +0 -131
- package/src/store/commands/add.ts +0 -231
- package/src/store/commands/init.spec.ts +0 -220
- package/src/store/commands/init.ts +0 -272
- package/src/store/commands/list.spec.ts +0 -175
- package/src/store/commands/list.ts +0 -102
- package/src/store/commands/prune.spec.ts +0 -120
- package/src/store/commands/prune.ts +0 -152
- package/src/store/commands/reindexs.spec.ts +0 -94
- package/src/store/commands/reindexs.ts +0 -97
- package/src/store/commands/remove.spec.ts +0 -97
- package/src/store/commands/remove.ts +0 -189
- package/src/store/index.spec.ts +0 -60
- package/src/store/index.ts +0 -49
- package/src/store/utils/resolve-store-name.spec.ts +0 -62
- package/src/store/utils/resolve-store-name.ts +0 -79
- package/src/test-helpers.spec.ts +0 -430
- package/src/tests/cli.integration.spec.ts +0 -1306
- package/src/toon.spec.ts +0 -183
- package/src/toon.ts +0 -462
- package/src/utils/git.spec.ts +0 -95
- package/src/utils/git.ts +0 -51
- package/src/utils/input.spec.ts +0 -326
- package/src/utils/input.ts +0 -150
- package/src/utils/paths.spec.ts +0 -235
- package/src/utils/paths.ts +0 -75
- package/src/utils/prompts.spec.ts +0 -23
- package/src/utils/prompts.ts +0 -88
- package/src/utils/resolve-default-store.spec.ts +0 -135
- package/src/utils/resolve-default-store.ts +0 -74
package/src/toon.spec.ts
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for toon.ts
|
|
3
|
-
*
|
|
4
|
-
* TOON (Token-Oriented Object Notation) encoder.
|
|
5
|
-
*
|
|
6
|
-
* @module cli/toon.spec
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect } from 'bun:test';
|
|
10
|
-
import { encode } from './toon';
|
|
11
|
-
|
|
12
|
-
// ============================================================================
|
|
13
|
-
// encode - primitives
|
|
14
|
-
// ============================================================================
|
|
15
|
-
|
|
16
|
-
describe('encode - primitives', () => {
|
|
17
|
-
it('should encode a string primitive', () => {
|
|
18
|
-
// Top-level primitive string: no key, just the value
|
|
19
|
-
expect(encode('hello')).toBe('hello');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should encode a number primitive', () => {
|
|
23
|
-
expect(encode(42)).toBe('42');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should encode a boolean primitive', () => {
|
|
27
|
-
expect(encode(true)).toBe('true');
|
|
28
|
-
expect(encode(false)).toBe('false');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should encode null as "null"', () => {
|
|
32
|
-
expect(encode(null)).toBe('null');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should encode undefined as "null"', () => {
|
|
36
|
-
expect(encode(undefined)).toBe('null');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should quote strings containing the delimiter (tab)', () => {
|
|
40
|
-
expect(encode('hello\tworld')).toBe('"hello\\tworld"');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should quote strings containing a colon (:)', () => {
|
|
44
|
-
expect(encode('key:value')).toBe('"key:value"');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should quote strings containing a newline', () => {
|
|
48
|
-
expect(encode('line1\nline2')).toBe('"line1\\nline2"');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should quote strings containing a double quote', () => {
|
|
52
|
-
expect(encode('"quoted"')).toBe('"\\"quoted\\""');
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// ============================================================================
|
|
57
|
-
// encode - flat objects
|
|
58
|
-
// ============================================================================
|
|
59
|
-
|
|
60
|
-
describe('encode - flat objects', () => {
|
|
61
|
-
it('should encode a single key-value pair', () => {
|
|
62
|
-
expect(encode({ name: 'test' })).toBe('name:test');
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should encode multiple key-value pairs with tab delimiter', () => {
|
|
66
|
-
expect(encode({ name: 'test', count: 42 })).toBe('name:test\tcount:42');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should use custom delimiter when specified', () => {
|
|
70
|
-
expect(encode({ a: 1, b: 2 }, { delimiter: '|' })).toBe('a:1|b:2');
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// ============================================================================
|
|
75
|
-
// encode - nested objects (no key folding)
|
|
76
|
-
// ============================================================================
|
|
77
|
-
|
|
78
|
-
describe('encode - nested objects (no key folding)', () => {
|
|
79
|
-
it('should encode nested object with inline braces', () => {
|
|
80
|
-
// "user:{name:Alice\trole:admin}"
|
|
81
|
-
expect(encode({ user: { name: 'Alice', role: 'admin' } })).toBe('user:{name:Alice\trole:admin}');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('should handle deeply nested objects', () => {
|
|
85
|
-
// "a:{b:{c:deep}}"
|
|
86
|
-
expect(encode({ a: { b: { c: 'deep' } } })).toBe('a:{b:{c:deep}}');
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// ============================================================================
|
|
91
|
-
// encode - key folding
|
|
92
|
-
// ============================================================================
|
|
93
|
-
|
|
94
|
-
describe('encode - key folding', () => {
|
|
95
|
-
it('should flatten nested object to dotted keys with keyFolding:safe', () => {
|
|
96
|
-
expect(encode({ user: { name: 'Alice', role: 'admin' } }, { keyFolding: 'safe' }))
|
|
97
|
-
.toBe('user.name:Alice\tuser.role:admin');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should handle multi-level nesting with keyFolding:safe', () => {
|
|
101
|
-
expect(encode(
|
|
102
|
-
{ user: { profile: { name: 'Alice' }, settings: { theme: 'dark' } } },
|
|
103
|
-
{ keyFolding: 'safe' },
|
|
104
|
-
)).toBe('user.profile.name:Alice\tuser.settings.theme:dark');
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// ============================================================================
|
|
109
|
-
// encode - arrays
|
|
110
|
-
// ============================================================================
|
|
111
|
-
|
|
112
|
-
describe('encode - arrays', () => {
|
|
113
|
-
it('should encode uniform array of objects in tabular format', () => {
|
|
114
|
-
const input = {
|
|
115
|
-
items: [
|
|
116
|
-
{ id: 1, name: 'Alice' },
|
|
117
|
-
{ id: 2, name: 'Bob' },
|
|
118
|
-
],
|
|
119
|
-
};
|
|
120
|
-
// items[2]{id\tname}:\n\t1\tAlice\n\t2\tBob
|
|
121
|
-
const expected = 'items[2]{id\tname}:\n\t1\tAlice\n\t2\tBob';
|
|
122
|
-
expect(encode(input)).toBe(expected);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('should encode empty array as quoted empty string (serializePrimitive path)', () => {
|
|
126
|
-
// isUniformArray returns false for empty arrays; the else branch in the
|
|
127
|
-
// object serialiser calls serializePrimitive([], delimiter) which does
|
|
128
|
-
// quoteString(String([])) → '""'
|
|
129
|
-
expect(encode({ items: [] })).toBe('items:""');
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should serialize non-uniform array via serializePrimitive (String coercion)', () => {
|
|
133
|
-
// Non-uniform arrays fall through to the else branch in the object
|
|
134
|
-
// serialiser → serializePrimitive → quoteString(String([...]))
|
|
135
|
-
const input = {
|
|
136
|
-
items: [
|
|
137
|
-
{ id: 1, name: 'Alice' },
|
|
138
|
-
{ id: 2 }, // missing 'name' key — non-uniform
|
|
139
|
-
],
|
|
140
|
-
};
|
|
141
|
-
expect(encode(input)).toBe('items:"[object Object],[object Object]"');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('should serialize primitive array via serializePrimitive (comma-joined string)', () => {
|
|
145
|
-
// Primitive arrays also go through serializePrimitive → quoteString(String([...]))
|
|
146
|
-
expect(encode({ tags: [
|
|
147
|
-
'a',
|
|
148
|
-
'b',
|
|
149
|
-
'c',
|
|
150
|
-
] })).toBe('tags:"a,b,c"');
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('should serialize array of mixed types via serializePrimitive', () => {
|
|
154
|
-
expect(encode({ mixed: [
|
|
155
|
-
1,
|
|
156
|
-
'two',
|
|
157
|
-
true,
|
|
158
|
-
] })).toBe('mixed:"1,two,true"');
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// ============================================================================
|
|
163
|
-
// encode - edge cases
|
|
164
|
-
// ============================================================================
|
|
165
|
-
|
|
166
|
-
describe('encode - edge cases', () => {
|
|
167
|
-
it('should encode an empty object', () => {
|
|
168
|
-
expect(encode({})).toBe('');
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('should handle object with array of uniform items alongside other fields', () => {
|
|
172
|
-
const input = {
|
|
173
|
-
title: 'Report',
|
|
174
|
-
rows: [
|
|
175
|
-
{ id: 1, val: 'a' },
|
|
176
|
-
{ id: 2, val: 'b' },
|
|
177
|
-
],
|
|
178
|
-
};
|
|
179
|
-
// "title:Report\trows[2]{id\tval}:\n\t1\ta\n\t2\tb"
|
|
180
|
-
const expected = 'title:Report\trows[2]{id\tval}:\n\t1\ta\n\t2\tb';
|
|
181
|
-
expect(encode(input)).toBe(expected);
|
|
182
|
-
});
|
|
183
|
-
});
|
package/src/toon.ts
DELETED
|
@@ -1,462 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TOON (Token-Oriented Object Notation) encoder.
|
|
3
|
-
*
|
|
4
|
-
* A compact serialization format optimized for LLM context consumption,
|
|
5
|
-
* achieving approximately 40% token reduction compared to JSON while
|
|
6
|
-
* improving parsing accuracy from 70% to 74% in LLM benchmarks.
|
|
7
|
-
*
|
|
8
|
-
* ## Key Features
|
|
9
|
-
*
|
|
10
|
-
* - **Tab delimiters** - Uses tabs between key-value pairs instead of JSON's
|
|
11
|
-
* commas and braces, maximizing token efficiency
|
|
12
|
-
* - **Key folding** - Collapses nested object paths to dotted notation
|
|
13
|
-
* (e.g., `user.profile.name` instead of nested objects)
|
|
14
|
-
* - **Tabular arrays** - Uniform object arrays use a compact header+rows format,
|
|
15
|
-
* dramatically reducing repetition
|
|
16
|
-
*
|
|
17
|
-
* ## Format Specification
|
|
18
|
-
*
|
|
19
|
-
* | JSON Construct | TOON Equivalent |
|
|
20
|
-
* |----------------|-----------------|
|
|
21
|
-
* | `{"key": "value"}` | `key:value` |
|
|
22
|
-
* | `{"a": 1, "b": 2}` | `a:1\tb:2` |
|
|
23
|
-
* | Nested object | `parent.child:value` (with key folding) |
|
|
24
|
-
* | Uniform array | `items[N]{col1\tcol2}:\n\tval1\tval2` |
|
|
25
|
-
*
|
|
26
|
-
* ## Token Efficiency
|
|
27
|
-
*
|
|
28
|
-
* TOON reduces token count through:
|
|
29
|
-
* 1. Eliminating JSON syntax tokens (`{`, `}`, `[`, `]`, `,`, `"`)
|
|
30
|
-
* 2. Using single-character delimiters (tabs, colons)
|
|
31
|
-
* 3. Deduplicating keys in tabular array format
|
|
32
|
-
*
|
|
33
|
-
* Typical savings:
|
|
34
|
-
* - Simple objects: ~30% reduction
|
|
35
|
-
* - Nested objects with key folding: ~40% reduction
|
|
36
|
-
* - Arrays of uniform objects: ~50% reduction
|
|
37
|
-
*
|
|
38
|
-
* @module cli/toon
|
|
39
|
-
*
|
|
40
|
-
* @example Basic object encoding
|
|
41
|
-
* ```ts
|
|
42
|
-
* import { encode } from './toon';
|
|
43
|
-
*
|
|
44
|
-
* encode({ name: 'test', count: 42 });
|
|
45
|
-
* // Output: "name:test\tcount:42"
|
|
46
|
-
* ```
|
|
47
|
-
*
|
|
48
|
-
* @example Key folding for nested objects
|
|
49
|
-
* ```ts
|
|
50
|
-
* encode(
|
|
51
|
-
* { user: { name: 'Alice', role: 'admin' } },
|
|
52
|
-
* { keyFolding: 'safe' }
|
|
53
|
-
* );
|
|
54
|
-
* // Output: "user.name:Alice\tuser.role:admin"
|
|
55
|
-
* //
|
|
56
|
-
* // Without key folding:
|
|
57
|
-
* // "user:{name:Alice\trole:admin}"
|
|
58
|
-
* ```
|
|
59
|
-
*
|
|
60
|
-
* @example Tabular arrays (automatic for uniform object arrays)
|
|
61
|
-
* ```ts
|
|
62
|
-
* encode({
|
|
63
|
-
* items: [
|
|
64
|
-
* { id: 1, name: 'Widget' },
|
|
65
|
-
* { id: 2, name: 'Gadget' },
|
|
66
|
-
* ]
|
|
67
|
-
* });
|
|
68
|
-
* // Output:
|
|
69
|
-
* // "items[2]{id\tname}:
|
|
70
|
-
* // \t1\tWidget
|
|
71
|
-
* // \t2\tGadget"
|
|
72
|
-
* //
|
|
73
|
-
* // Equivalent JSON (32 tokens):
|
|
74
|
-
* // {"items":[{"id":1,"name":"Widget"},{"id":2,"name":"Gadget"}]}
|
|
75
|
-
* // TOON version (18 tokens): ~44% reduction
|
|
76
|
-
* ```
|
|
77
|
-
*
|
|
78
|
-
* @see {@link ToonOptions} for configuration options
|
|
79
|
-
* @see {@link encode} for the main encoding function
|
|
80
|
-
*/
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Configuration options for TOON encoding.
|
|
84
|
-
*
|
|
85
|
-
* @example Using custom delimiter
|
|
86
|
-
* ```ts
|
|
87
|
-
* encode({ a: 1, b: 2 }, { delimiter: '|' });
|
|
88
|
-
* // Output: "a:1|b:2"
|
|
89
|
-
* ```
|
|
90
|
-
*
|
|
91
|
-
* @example Enabling key folding
|
|
92
|
-
* ```ts
|
|
93
|
-
* encode({ config: { debug: true } }, { keyFolding: 'safe' });
|
|
94
|
-
* // Output: "config.debug:true"
|
|
95
|
-
* ```
|
|
96
|
-
*/
|
|
97
|
-
export interface ToonOptions {
|
|
98
|
-
/**
|
|
99
|
-
* Character used to separate key-value pairs.
|
|
100
|
-
*
|
|
101
|
-
* The tab character (`\t`) is used by default as it provides optimal
|
|
102
|
-
* token efficiency in most LLM tokenizers (single token per delimiter).
|
|
103
|
-
*
|
|
104
|
-
* @default '\t'
|
|
105
|
-
*/
|
|
106
|
-
delimiter?: string;
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Controls how nested objects are serialized.
|
|
110
|
-
*
|
|
111
|
-
* - `'none'` - Nested objects are serialized inline with braces:
|
|
112
|
-
* `parent:{child:value}`
|
|
113
|
-
* - `'safe'` - Nested paths are collapsed to dotted notation:
|
|
114
|
-
* `parent.child:value`
|
|
115
|
-
*
|
|
116
|
-
* Key folding (`'safe'`) typically produces more compact output and is
|
|
117
|
-
* easier for LLMs to parse, but may cause key collisions if object
|
|
118
|
-
* keys contain dots.
|
|
119
|
-
*
|
|
120
|
-
* @default 'none'
|
|
121
|
-
*/
|
|
122
|
-
keyFolding?: 'safe' | 'none';
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Default encoding options.
|
|
127
|
-
*
|
|
128
|
-
* - `delimiter`: Tab character for maximum token efficiency
|
|
129
|
-
* - `keyFolding`: Disabled by default for safety (avoids key collisions)
|
|
130
|
-
*/
|
|
131
|
-
const DEFAULT_OPTIONS: Required<ToonOptions> = {
|
|
132
|
-
delimiter: '\t',
|
|
133
|
-
keyFolding: 'none',
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Checks if a string needs quoting (contains delimiter, newline, or special chars).
|
|
138
|
-
*
|
|
139
|
-
* Values containing these characters must be quoted to preserve TOON format integrity:
|
|
140
|
-
* - The delimiter character (default: tab)
|
|
141
|
-
* - Newlines (`\n`, `\r`)
|
|
142
|
-
* - Double quotes (`"`)
|
|
143
|
-
* - Colons (`:`) - used as key-value separator
|
|
144
|
-
*
|
|
145
|
-
* @param value - The string value to check
|
|
146
|
-
* @param delimiter - The current delimiter character
|
|
147
|
-
* @returns `true` if the value requires quoting
|
|
148
|
-
*/
|
|
149
|
-
const needsQuoting = (value: string, delimiter: string): boolean => {
|
|
150
|
-
return (
|
|
151
|
-
value.includes(delimiter) ||
|
|
152
|
-
value.includes('\n') ||
|
|
153
|
-
value.includes('\r') ||
|
|
154
|
-
value.includes('"') ||
|
|
155
|
-
value.includes(':')
|
|
156
|
-
);
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Quotes a string value for TOON output.
|
|
161
|
-
*
|
|
162
|
-
* Uses JSON string escaping rules to ensure special characters are
|
|
163
|
-
* properly escaped in the output.
|
|
164
|
-
*
|
|
165
|
-
* @param value - The string to quote
|
|
166
|
-
* @returns JSON-escaped quoted string
|
|
167
|
-
*/
|
|
168
|
-
const quoteString = (value: string): string => {
|
|
169
|
-
return JSON.stringify(value);
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Serializes a primitive value to its TOON string representation.
|
|
174
|
-
*
|
|
175
|
-
* Handles the following types:
|
|
176
|
-
* - `null`/`undefined` → `"null"`
|
|
177
|
-
* - `string` → quoted if contains special chars, otherwise raw
|
|
178
|
-
* - `number`/`boolean` → string representation
|
|
179
|
-
* - Other types → JSON-stringified
|
|
180
|
-
*
|
|
181
|
-
* @param value - The primitive value to serialize
|
|
182
|
-
* @param delimiter - The delimiter used for quoting decisions
|
|
183
|
-
* @returns TOON string representation of the value
|
|
184
|
-
*/
|
|
185
|
-
const serializePrimitive = (value: unknown, delimiter: string): string => {
|
|
186
|
-
if (value === null) return 'null';
|
|
187
|
-
if (value === undefined) return 'null';
|
|
188
|
-
if (typeof value === 'string') {
|
|
189
|
-
return needsQuoting(value, delimiter) ? quoteString(value) : value;
|
|
190
|
-
}
|
|
191
|
-
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
192
|
-
return String(value);
|
|
193
|
-
}
|
|
194
|
-
return quoteString(String(value));
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Checks if an array contains uniform objects (all same keys).
|
|
199
|
-
*
|
|
200
|
-
* Uniform arrays qualify for the more compact tabular format where
|
|
201
|
-
* column headers are specified once and rows contain only values.
|
|
202
|
-
*
|
|
203
|
-
* An array is uniform if:
|
|
204
|
-
* 1. It contains at least one element
|
|
205
|
-
* 2. All elements are non-null objects (not arrays)
|
|
206
|
-
* 3. All objects have exactly the same keys
|
|
207
|
-
*
|
|
208
|
-
* @param arr - The array to check
|
|
209
|
-
* @returns Type predicate indicating if array contains uniform objects
|
|
210
|
-
*/
|
|
211
|
-
const isUniformArray = (arr: unknown[]): arr is Record<string, unknown>[] => {
|
|
212
|
-
if (arr.length === 0) return false;
|
|
213
|
-
if (!arr.every((item) => typeof item === 'object' && item !== null && !Array.isArray(item))) {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
const first = arr[0] as Record<string, unknown>;
|
|
217
|
-
const firstKeys = Object.keys(first).sort().join(',');
|
|
218
|
-
return arr.every(
|
|
219
|
-
(item) =>
|
|
220
|
-
Object.keys(item as object)
|
|
221
|
-
.sort()
|
|
222
|
-
.join(',') === firstKeys,
|
|
223
|
-
);
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Serializes a uniform array in tabular format.
|
|
228
|
-
*
|
|
229
|
-
* Tabular format provides significant token savings for arrays of objects
|
|
230
|
-
* with identical structure by specifying column headers once.
|
|
231
|
-
*
|
|
232
|
-
* Format: `key[count]{col1\tcol2}:\n\tval1\tval2\n\tval3\tval4`
|
|
233
|
-
*
|
|
234
|
-
* @param arr - Array of uniform objects to serialize
|
|
235
|
-
* @param key - The property name for this array
|
|
236
|
-
* @param delimiter - Character used between values
|
|
237
|
-
* @returns Tabular TOON representation
|
|
238
|
-
*
|
|
239
|
-
* @example
|
|
240
|
-
* ```ts
|
|
241
|
-
* serializeTabularArray(
|
|
242
|
-
* [{ id: 1, name: 'a' }, { id: 2, name: 'b' }],
|
|
243
|
-
* 'items',
|
|
244
|
-
* '\t'
|
|
245
|
-
* );
|
|
246
|
-
* // Returns:
|
|
247
|
-
* // "items[2]{id\tname}:
|
|
248
|
-
* // \t1\ta
|
|
249
|
-
* // \t2\tb"
|
|
250
|
-
* ```
|
|
251
|
-
*/
|
|
252
|
-
const serializeTabularArray = (
|
|
253
|
-
arr: Record<string, unknown>[],
|
|
254
|
-
key: string,
|
|
255
|
-
delimiter: string,
|
|
256
|
-
): string => {
|
|
257
|
-
if (arr.length === 0) return `${key}:[]`;
|
|
258
|
-
|
|
259
|
-
const first = arr[0] as Record<string, unknown>;
|
|
260
|
-
const headers = Object.keys(first);
|
|
261
|
-
const headerLine = `${key}[${arr.length}]{${headers.join(delimiter)}}:`;
|
|
262
|
-
|
|
263
|
-
const rows = arr.map((item) => {
|
|
264
|
-
const values = headers.map((h) => serializePrimitive(item[h], delimiter));
|
|
265
|
-
return delimiter + values.join(delimiter);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
return [
|
|
269
|
-
headerLine, ...rows,
|
|
270
|
-
].join('\n');
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Flattens an object with key folding (dotted notation).
|
|
275
|
-
*
|
|
276
|
-
* Recursively traverses nested objects and creates a flat object with
|
|
277
|
-
* dotted key paths. Arrays are preserved as-is (not flattened).
|
|
278
|
-
*
|
|
279
|
-
* @param obj - The object to flatten
|
|
280
|
-
* @param prefix - Current key prefix for recursion (empty string at root)
|
|
281
|
-
* @returns Flattened object with dotted keys
|
|
282
|
-
*
|
|
283
|
-
* @example
|
|
284
|
-
* ```ts
|
|
285
|
-
* flattenObject({ user: { name: 'Alice', settings: { theme: 'dark' } } });
|
|
286
|
-
* // Returns: { 'user.name': 'Alice', 'user.settings.theme': 'dark' }
|
|
287
|
-
* ```
|
|
288
|
-
*/
|
|
289
|
-
const flattenObject = (
|
|
290
|
-
obj: Record<string, unknown>,
|
|
291
|
-
prefix: string = '',
|
|
292
|
-
): Record<string, unknown> => {
|
|
293
|
-
const result: Record<string, unknown> = {};
|
|
294
|
-
|
|
295
|
-
for (const [
|
|
296
|
-
key, value,
|
|
297
|
-
] of Object.entries(obj)) {
|
|
298
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
299
|
-
|
|
300
|
-
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
301
|
-
Object.assign(result, flattenObject(value as Record<string, unknown>, fullKey));
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
result[fullKey] = value;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return result;
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Serializes a value to TOON format (internal recursive function).
|
|
313
|
-
*
|
|
314
|
-
* Handles all value types and applies the appropriate serialization strategy:
|
|
315
|
-
* - Primitives: Direct serialization with optional quoting
|
|
316
|
-
* - Arrays: Tabular format for uniform objects, JSON for mixed/primitive arrays
|
|
317
|
-
* - Objects: Key folding or inline braces based on options
|
|
318
|
-
*
|
|
319
|
-
* @param value - The value to serialize
|
|
320
|
-
* @param options - Encoding options (fully resolved with defaults)
|
|
321
|
-
* @param key - Optional key name when serializing as an object property
|
|
322
|
-
* @returns TOON-formatted string
|
|
323
|
-
*/
|
|
324
|
-
const serializeValue = (value: unknown, options: Required<ToonOptions>, key?: string): string => {
|
|
325
|
-
const { delimiter, keyFolding } = options;
|
|
326
|
-
|
|
327
|
-
// Handle primitives
|
|
328
|
-
if (value === null || value === undefined) {
|
|
329
|
-
return key ? `${key}:null` : 'null';
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (typeof value !== 'object') {
|
|
333
|
-
const serialized = serializePrimitive(value, delimiter);
|
|
334
|
-
return key ? `${key}:${serialized}` : serialized;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Handle arrays
|
|
338
|
-
if (Array.isArray(value)) {
|
|
339
|
-
if (isUniformArray(value) && key) {
|
|
340
|
-
return serializeTabularArray(value, key, delimiter);
|
|
341
|
-
}
|
|
342
|
-
// Non-uniform arrays: serialize as JSON array
|
|
343
|
-
const serialized = JSON.stringify(value);
|
|
344
|
-
return key ? `${key}:${serialized}` : serialized;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Handle objects
|
|
348
|
-
let obj = value as Record<string, unknown>;
|
|
349
|
-
if (keyFolding === 'safe') {
|
|
350
|
-
obj = flattenObject(obj);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const entries = Object.entries(obj);
|
|
354
|
-
const parts: string[] = [];
|
|
355
|
-
|
|
356
|
-
for (const [
|
|
357
|
-
k, v,
|
|
358
|
-
] of entries) {
|
|
359
|
-
if (Array.isArray(v) && isUniformArray(v)) {
|
|
360
|
-
parts.push(serializeTabularArray(v, k, delimiter));
|
|
361
|
-
}
|
|
362
|
-
else if (
|
|
363
|
-
typeof v === 'object' &&
|
|
364
|
-
v !== null &&
|
|
365
|
-
!Array.isArray(v) &&
|
|
366
|
-
keyFolding !== 'safe'
|
|
367
|
-
) {
|
|
368
|
-
// Nested object without key folding - serialize inline
|
|
369
|
-
parts.push(`${k}:{${serializeValue(v, options)}}`);
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
const serialized = serializePrimitive(v, delimiter);
|
|
373
|
-
parts.push(`${k}:${serialized}`);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const result = parts.join(delimiter);
|
|
378
|
-
return key ? `${key}:{${result}}` : result;
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Encodes a value to TOON format.
|
|
383
|
-
*
|
|
384
|
-
* TOON (Token-Oriented Object Notation) is a compact serialization format
|
|
385
|
-
* designed to minimize token consumption in LLM contexts while remaining
|
|
386
|
-
* human-readable and easily parseable by language models.
|
|
387
|
-
*
|
|
388
|
-
* @param value - The value to encode (must be JSON-serializable)
|
|
389
|
-
* @param options - Encoding options for customizing output format
|
|
390
|
-
* @returns TOON-encoded string representation
|
|
391
|
-
*
|
|
392
|
-
* @example Basic object encoding
|
|
393
|
-
* ```ts
|
|
394
|
-
* const data = { name: 'test', count: 42 };
|
|
395
|
-
* encode(data);
|
|
396
|
-
* // Returns: "name:test\tcount:42"
|
|
397
|
-
* //
|
|
398
|
-
* // Equivalent JSON: {"name":"test","count":42}
|
|
399
|
-
* // Token savings: ~35%
|
|
400
|
-
* ```
|
|
401
|
-
*
|
|
402
|
-
* @example Nested objects with key folding
|
|
403
|
-
* ```ts
|
|
404
|
-
* const nested = {
|
|
405
|
-
* user: {
|
|
406
|
-
* profile: { name: 'Alice', email: 'alice@example.com' },
|
|
407
|
-
* settings: { theme: 'dark' }
|
|
408
|
-
* }
|
|
409
|
-
* };
|
|
410
|
-
*
|
|
411
|
-
* // Without key folding (default):
|
|
412
|
-
* encode(nested);
|
|
413
|
-
* // Returns: "user:{profile:{name:Alice\temail:alice@example.com}\tsettings:{theme:dark}}"
|
|
414
|
-
*
|
|
415
|
-
* // With key folding:
|
|
416
|
-
* encode(nested, { keyFolding: 'safe' });
|
|
417
|
-
* // Returns: "user.profile.name:Alice\tuser.profile.email:alice@example.com\tuser.settings.theme:dark"
|
|
418
|
-
* ```
|
|
419
|
-
*
|
|
420
|
-
* @example Tabular arrays (automatic for uniform object arrays)
|
|
421
|
-
* ```ts
|
|
422
|
-
* const users = {
|
|
423
|
-
* users: [
|
|
424
|
-
* { id: 1, name: 'Alice', role: 'admin' },
|
|
425
|
-
* { id: 2, name: 'Bob', role: 'user' },
|
|
426
|
-
* { id: 3, name: 'Charlie', role: 'user' },
|
|
427
|
-
* ]
|
|
428
|
-
* };
|
|
429
|
-
*
|
|
430
|
-
* encode(users);
|
|
431
|
-
* // Returns:
|
|
432
|
-
* // "users[3]{id\tname\trole}:
|
|
433
|
-
* // \t1\tAlice\tadmin
|
|
434
|
-
* // \t2\tBob\tuser
|
|
435
|
-
* // \t3\tCharlie\tuser"
|
|
436
|
-
* //
|
|
437
|
-
* // Equivalent JSON (87 chars, ~25 tokens):
|
|
438
|
-
* // {"users":[{"id":1,"name":"Alice","role":"admin"},{"id":2,"name":"Bob","role":"user"},{"id":3,"name":"Charlie","role":"user"}]}
|
|
439
|
-
* //
|
|
440
|
-
* // TOON (67 chars, ~15 tokens): ~40% token reduction
|
|
441
|
-
* ```
|
|
442
|
-
*
|
|
443
|
-
* @example Mixed content
|
|
444
|
-
* ```ts
|
|
445
|
-
* encode({
|
|
446
|
-
* title: 'Report',
|
|
447
|
-
* metadata: { version: 1 },
|
|
448
|
-
* tags: ['urgent', 'review'] // Non-uniform arrays use JSON format
|
|
449
|
-
* }, { keyFolding: 'safe' });
|
|
450
|
-
* // Returns: "title:Report\tmetadata.version:1\ttags:[\"urgent\",\"review\"]"
|
|
451
|
-
* ```
|
|
452
|
-
*
|
|
453
|
-
* @see {@link ToonOptions} for available configuration options
|
|
454
|
-
*/
|
|
455
|
-
export const encode = (value: unknown, options?: ToonOptions): string => {
|
|
456
|
-
const mergedOptions: Required<ToonOptions> = {
|
|
457
|
-
...DEFAULT_OPTIONS,
|
|
458
|
-
...options,
|
|
459
|
-
};
|
|
460
|
-
|
|
461
|
-
return serializeValue(value, mergedOptions);
|
|
462
|
-
};
|