@arkadia/ai-data-format 1.0.0

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 (54) hide show
  1. package/dist/config.d.ts +75 -0
  2. package/dist/config.d.ts.map +1 -0
  3. package/dist/config.js +28 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/core/Decoder.d.ts +88 -0
  6. package/dist/core/Decoder.d.ts.map +1 -0
  7. package/dist/core/Decoder.js +968 -0
  8. package/dist/core/Decoder.js.map +1 -0
  9. package/dist/core/Encoder.d.ts +26 -0
  10. package/dist/core/Encoder.d.ts.map +1 -0
  11. package/dist/core/Encoder.js +368 -0
  12. package/dist/core/Encoder.js.map +1 -0
  13. package/dist/core/Parser.d.ts +6 -0
  14. package/dist/core/Parser.d.ts.map +1 -0
  15. package/dist/core/Parser.js +132 -0
  16. package/dist/core/Parser.js.map +1 -0
  17. package/dist/index.d.ts +22 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +46 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/models/Meta.d.ts +48 -0
  22. package/dist/models/Meta.d.ts.map +1 -0
  23. package/dist/models/Meta.js +113 -0
  24. package/dist/models/Meta.js.map +1 -0
  25. package/dist/models/Node.d.ts +42 -0
  26. package/dist/models/Node.d.ts.map +1 -0
  27. package/dist/models/Node.js +179 -0
  28. package/dist/models/Node.js.map +1 -0
  29. package/dist/models/Schema.d.ts +55 -0
  30. package/dist/models/Schema.d.ts.map +1 -0
  31. package/dist/models/Schema.js +175 -0
  32. package/dist/models/Schema.js.map +1 -0
  33. package/package.json +32 -0
  34. package/src/config.ts +102 -0
  35. package/src/core/Decoder.ts +1074 -0
  36. package/src/core/Encoder.ts +443 -0
  37. package/src/core/Parser.ts +150 -0
  38. package/src/index.ts +46 -0
  39. package/src/models/Meta.ts +135 -0
  40. package/src/models/Node.ts +212 -0
  41. package/src/models/Schema.ts +222 -0
  42. package/tests/00.meta.test.ts +31 -0
  43. package/tests/00.node.test.ts +54 -0
  44. package/tests/00.primitive.test.ts +108 -0
  45. package/tests/00.schema.test.ts +41 -0
  46. package/tests/01.schema.test.ts +70 -0
  47. package/tests/02.data.test.ts +89 -0
  48. package/tests/03.errors.test.ts +71 -0
  49. package/tests/04.list.test.ts +225 -0
  50. package/tests/05.record.test.ts +82 -0
  51. package/tests/06.meta.test.ts +506 -0
  52. package/tests/utils.ts +69 -0
  53. package/tsconfig.json +46 -0
  54. package/vitest.config.ts +9 -0
@@ -0,0 +1,108 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { encode, decode, MetaInfo, parse } from '../src/index';
3
+ import { assertRoundtrip } from './utils';
4
+
5
+ // ==================================================================================
6
+ // 1. DECODING TESTS (String -> Node)
7
+ // ==================================================================================
8
+
9
+ describe('AI Data Primitives', () => {
10
+ it('should encode raw object', () => {
11
+ const input = { foo: "bar" };
12
+ const expected = '<foo:string>("bar")';
13
+ assertRoundtrip(input, expected);
14
+ });
15
+
16
+ it.each([
17
+ // [Input Text, Expected Value, Expected Encoded Output]
18
+ ["123", 123, "<number>123"],
19
+ ["-50", -50, "<number>-50"],
20
+ ['"hello"', "hello", '<string>"hello"'],
21
+ ['"hello world"', "hello world", '<string>"hello world"'],
22
+ ["true", true, "<bool>true"],
23
+ ["false", false, "<bool>false"],
24
+ ["null", null, "<null>null"],
25
+ ])('should decode and encode primitive: %s', (text, expectedVal, expectedEnc) => {
26
+ // 1. DECODE
27
+ // We pass an options object (assuming the latest signature: decode(text, { debug: false }))
28
+ const res = decode(text, { debug: false });
29
+
30
+ expect(res.errors, `Parsing failed for input: ${text}`).toHaveLength(0);
31
+ expect(res.node.value).toBe(expectedVal);
32
+ expect(res.node.isPrimitive).toBe(true);
33
+
34
+ // 2. ENCODE
35
+ const encodedStr = encode(res.node, { colorize: false, compact: true }).trim();
36
+
37
+ expect(encodedStr).toBe(expectedEnc);
38
+ });
39
+
40
+ it('should decode floats', () => {
41
+ const cases: [string, number][] = [
42
+ ["12.34", 12.34],
43
+ ["-0.005", -0.005],
44
+ ["0.0", 0.0]
45
+ ];
46
+
47
+ cases.forEach(([text, expected]) => {
48
+ const res = decode(text);
49
+ expect(res.errors).toHaveLength(0);
50
+ expect(res.node.value).toBe(expected);
51
+ expect(typeof res.node.value).toBe('number');
52
+ });
53
+ });
54
+
55
+ it('should decode named record', () => {
56
+ const text = '{id: 1, name: "Test"}';
57
+ const res = decode(text, { debug: false });
58
+ expect(res.errors).toHaveLength(0);
59
+
60
+ const node = res.node;
61
+
62
+ expect(node.isRecord).toBe(true);
63
+ expect(node.fields["id"].value).toBe(1);
64
+ expect(node.fields["name"].value).toBe("Test");
65
+
66
+ assertRoundtrip(
67
+ node,
68
+ '<id:number,name:string>(1,"Test")',
69
+ false
70
+ );
71
+ });
72
+
73
+ it('should decode positional record', () => {
74
+ const text = '(10, "Alice")';
75
+ const res = decode(text, { debug: false });
76
+ expect(res.errors).toHaveLength(0);
77
+
78
+ const node = res.node;
79
+
80
+ expect(node.isRecord).toBe(true);
81
+
82
+ // Verify if the parser mapped positional fields to _0, _1
83
+ expect(node.fields["_0"].value).toBe(10);
84
+ expect(node.fields["_1"].value).toBe("Alice");
85
+
86
+ assertRoundtrip(
87
+ node,
88
+ '<_0:number,_1:string>(10,"Alice")',
89
+ false
90
+ );
91
+ });
92
+
93
+ it('should decode raw string', () => {
94
+ const text = "{color: red, status: active}";
95
+ const res = decode(text, { debug: false });
96
+ expect(res.errors).toHaveLength(0);
97
+
98
+ const node = res.node;
99
+ expect(node.fields["color"].value).toBe("red");
100
+ expect(node.fields["status"].value).toBe("active");
101
+
102
+ assertRoundtrip(
103
+ node,
104
+ '<color:string,status:string>("red","active")',
105
+ false
106
+ );
107
+ });
108
+ });
@@ -0,0 +1,41 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { Schema, SchemaKind } from '../src/index';
3
+
4
+ describe('AI Schema test', () => {
5
+ it('should encode raw object', () => {
6
+ const schema = new Schema(
7
+ SchemaKind.DICT,
8
+ {
9
+ name: "TestSchema",
10
+ comments: ["This is a comment"],
11
+ attr: { foo: "bar" },
12
+ tags: ["tag1", "tag2"],
13
+ required: true
14
+
15
+ });
16
+ const expected = '<Schema(DICT) name="TestSchema" !required attr=["foo"] tags=[tag1, tag2] comments=1>';
17
+ const expected_val = {
18
+ "comments": [
19
+ "This is a comment"
20
+ ],
21
+ "attr": {
22
+ "foo": "bar"
23
+ },
24
+ "tags": [
25
+ "tag1",
26
+ "tag2"
27
+ ],
28
+ "_fieldsList": [],
29
+ "_fieldsMap": {},
30
+ "kind": "DICT",
31
+ "typeName": "any",
32
+ "name": "TestSchema",
33
+ "element": null,
34
+ "key": null,
35
+ "value": null,
36
+ "required": true
37
+ }
38
+ expect(schema).toMatchObject(expected_val);
39
+ expect(schema.toString()).toBe(expected);
40
+ });
41
+ });
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { decode } from '../src/index';
3
+ import { assertRoundtrip } from './utils';
4
+
5
+ // ==================================================================================
6
+ // 2. SCHEMA DEFINITION & TYPING
7
+ // ==================================================================================
8
+
9
+ describe('AI Data Schema Definitions', () => {
10
+
11
+ it('should handle schema definition and usage', () => {
12
+ /**
13
+ * Validates that a type defined with @Type<...> is correctly applied
14
+ * to the following value.
15
+ */
16
+ // Define schema first, then use it explicitly
17
+ const fullText = '@User<id:int, name:string> @User(1, "Admin")';
18
+ const res = decode(fullText, { debug: false });
19
+
20
+ expect(res.errors).toHaveLength(0);
21
+
22
+ const node = res.node;
23
+ // Check if schema is linked correctly
24
+ expect(node.schema).not.toBeNull();
25
+ expect(node.schema?.typeName).toBe("User");
26
+
27
+ // Since we have a schema, positional arguments should be mapped to fields
28
+ // Check by key (Decoder maps positional to fields if schema exists)
29
+ expect(node.fields["id"].value).toBe(1);
30
+ expect(node.fields["name"].value).toBe("Admin");
31
+
32
+ assertRoundtrip(
33
+ node,
34
+ '@User<id:number,name:string>(1,"Admin")',
35
+ false
36
+ );
37
+ });
38
+
39
+ it('should handle nested schema structure', () => {
40
+ /**
41
+ * Validates nested structural types.
42
+ */
43
+ // We define Profile, then User uses it.
44
+ const text = `
45
+ @Profile<level:int>
46
+ @User<id:int, profile: @Profile>
47
+ @User(1, {level: 99})
48
+ `;
49
+
50
+ const res = decode(text, { debug: false });
51
+
52
+ expect(res.errors).toHaveLength(0);
53
+ const node = res.node;
54
+
55
+ // id should be 1
56
+ expect(node.fields["id"].value).toBe(1);
57
+
58
+ // profile should be a node
59
+ const profileNode = node.fields["profile"];
60
+ expect(profileNode).toBeDefined();
61
+ expect(profileNode.fields["level"].value).toBe(99);
62
+
63
+ assertRoundtrip(
64
+ node,
65
+ "@User<id:number,profile:@Profile<level:number>>(1,(99))",
66
+ false
67
+ );
68
+ });
69
+
70
+ });
@@ -0,0 +1,89 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { encode, decode } from '../src/index';
3
+ import { assertRoundtrip } from './utils';
4
+
5
+ // ==================================================================================
6
+ // 3. ENCODING TESTS (JS Object -> String)
7
+ // ==================================================================================
8
+
9
+ describe('AI Data Encoding', () => {
10
+
11
+ it('should encode simple dict', () => {
12
+ /**
13
+ * Validates encoding a JS Object to AI.DATA format.
14
+ */
15
+ const data = { x: 10, y: 20 };
16
+
17
+ const result = encode(data, { compact: true });
18
+ const expected = '<x:number,y:number>(10,20)';
19
+
20
+ expect(result).toBe(expected);
21
+
22
+ assertRoundtrip(
23
+ result,
24
+ expected,
25
+ false
26
+ );
27
+ });
28
+
29
+ it('should encode list of objects', () => {
30
+ /**
31
+ * Validates encoding a list of objects.
32
+ */
33
+ const data = [{ name: "A", val: 1 }, { name: "B", val: 2 }];
34
+
35
+ const result = encode(data, { compact: true, colorize: false });
36
+ const expected = '<[name:string,val:number]>[("A",1),("B",2)]';
37
+
38
+ expect(result).toBe(expected);
39
+
40
+ assertRoundtrip(
41
+ result,
42
+ expected,
43
+ false
44
+ );
45
+ });
46
+
47
+ it('should handle round trip consistency', () => {
48
+ /**
49
+ * Golden Test: Encode -> Decode -> Compare.
50
+ */
51
+ const originalData = [
52
+ { id: 1, active: true, tags: ["a", "b"] },
53
+ { id: 2, active: false, tags: ["c"] },
54
+ ];
55
+
56
+ const expected = '<[id:number,active:bool,tags:[string]]>[(1,true,["a","b"]),(2,false,["c"])]';
57
+
58
+ // 1. Encode
59
+ // Note: converted snake_case config keys to camelCase for TS
60
+ const encodedText = encode(
61
+ originalData,
62
+ { compact: true, includeSchema: true, colorize: false }
63
+ );
64
+
65
+ expect(encodedText).toBe(expected);
66
+ assertRoundtrip(encodedText, expected, false);
67
+
68
+ // 2. Decode
69
+ const res = decode(encodedText, { debug: false });
70
+
71
+ // Optional: inspect schema structure
72
+
73
+ expect(res.errors).toHaveLength(0);
74
+
75
+ // 3. Convert back to JS object (equivalent to .dict() in Python)
76
+ const decodedData = res.node.dict()
77
+
78
+ // 4. Compare
79
+ expect(decodedData).toHaveLength(2);
80
+ expect(decodedData[0].id).toBe(1);
81
+ expect(decodedData[0].active).toBe(true);
82
+ expect(decodedData[0].tags).toEqual(["a", "b"]);
83
+ expect(decodedData[1].active).toBe(false);
84
+
85
+ // Deep equality check
86
+ expect(decodedData).toEqual(originalData);
87
+ });
88
+
89
+ });
@@ -0,0 +1,71 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { encode, decode } from '../src/index';
3
+
4
+ // ==================================================================================
5
+ // 4. ROUND TRIP (Encode -> Decode) with Errors
6
+ // ==================================================================================
7
+
8
+ describe('A Data Error Handling', () => {
9
+
10
+ it('should report errors on unstripped ANSI codes', () => {
11
+ /**
12
+ * Validates that the Decoder handles ANSI codes gracefully (by reporting errors)
13
+ * if they are not stripped out before decoding.
14
+ */
15
+ const originalData = [{ id: 1, active: true }, { id: 2, active: false }];
16
+
17
+ // 1. Encode WITH colors
18
+ // Note: We use camelCase keys for config in TypeScript
19
+ const encodedText = encode(
20
+ originalData,
21
+ {
22
+ compact: true,
23
+ includeSchema: true,
24
+ colorize: true,
25
+ }
26
+ );
27
+
28
+ // 2. Decode WITHOUT stripping colors (simulate user error)
29
+ // Pass removeAnsiColors: false explicitely
30
+ const res = decode(encodedText, { removeAnsiColors: false, debug: false });
31
+
32
+ // 3. Assertions
33
+ expect(res.errors.length).toBeGreaterThan(0); // Decoder should report errors on raw ANSI codes
34
+
35
+ // Optional: Verify error content
36
+ const messages = res.errors.map(e => e.message);
37
+ const hasUnexpectedChar = messages.some(m => m.includes("Unexpected character"));
38
+ expect(hasUnexpectedChar).toBe(true);
39
+ });
40
+
41
+ // ==================================================================================
42
+ // 5. PARSING ERROR SCENARIOS
43
+ // ==================================================================================
44
+
45
+ it('should fail on unclosed list', () => {
46
+ /**
47
+ * Ensures parsing fails for malformed lists.
48
+ */
49
+ const text = "[1, 2, 3"; // Missing closing bracket
50
+ const res = decode(text, { debug: false });
51
+
52
+ expect(res.errors.length).toBeGreaterThan(0);
53
+
54
+ const msg = res.errors[0].message;
55
+ // Check for typical error messages related to missing tokens or EOF
56
+ const isExpectedError = msg.includes("Expected") || msg.includes("got") || msg.includes("EOF");
57
+ expect(isExpectedError).toBe(true);
58
+ });
59
+
60
+ it('should fail on unexpected character', () => {
61
+ /**
62
+ * Ensures parsing fails for illegal characters.
63
+ */
64
+ const text = "(1, ?)";
65
+ const res = decode(text, { debug: false });
66
+
67
+ expect(res.errors.length).toBeGreaterThan(0);
68
+ expect(res.errors[0].message).toContain("Unexpected character");
69
+ });
70
+
71
+ });
@@ -0,0 +1,225 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { encode, decode, parse, SchemaKind } from '../src/index';
3
+ import { assertRoundtrip } from './utils';
4
+
5
+ describe('AI Data List Handling', () => {
6
+
7
+ it('should decode list of primitives', () => {
8
+ /**
9
+ * Validates simple lists.
10
+ */
11
+ const text = "[1, 2, 3]";
12
+ const expected = "<[number]>[1,2,3]";
13
+
14
+ const res = decode(text, { debug: false });
15
+ expect(res.errors).toHaveLength(0);
16
+
17
+ const node = res.node;
18
+ // Check elements (Node logic stores simple lists in elements)
19
+ const values = node.elements.map(el => el.value);
20
+ expect(values).toEqual([1, 2, 3]);
21
+
22
+ assertRoundtrip(node, expected, false);
23
+ });
24
+
25
+ it('should infer mixed list type from first element', () => {
26
+ /**
27
+ * Tests if the list type is inferred based on the first element ("a" -> string).
28
+ * As a result, the number 3 (int) should be treated as a mismatch and marked with the <number> tag.
29
+ */
30
+ // 1. Input data: List starts with strings but has an int at the end
31
+ const data = { tests: ["a", "b", "c", 3] };
32
+
33
+ // 2. Encoding (inference happens here in Parser.ts)
34
+ const node = parse(data);
35
+
36
+ expect(node.schema).not.toBeNull();
37
+ expect(node.schema.isRecord).toBe(true);
38
+ expect(node.fields).toHaveProperty("tests");
39
+
40
+ const testsNode = node.fields["tests"];
41
+ expect(testsNode.isList).toBe(true);
42
+ expect(testsNode.schema).not.toBeNull();
43
+ expect(testsNode.schema.element).not.toBeNull();
44
+
45
+ expect(testsNode.schema.element?.kind).toBe(SchemaKind.PRIMITIVE);
46
+ expect(testsNode.schema.element?.typeName).toBe("string"); // Inferred from first element "a"
47
+
48
+ expect(testsNode.elements).toHaveLength(4);
49
+
50
+ const output = encode(data, { compact: true, colorize: false });
51
+
52
+ // We expect the list NOT to be [any], but [string] (implied or explicit),
53
+ // so strings will be "clean", and the number will get a tag.
54
+
55
+ // Check if 'a' is treated normally (as a string in a string list)
56
+ expect(output).toContain('"a"');
57
+
58
+ // KEY: Check if 3 got a tag because it doesn't match the inferred String type
59
+ // TS Encoder outputs <number>
60
+ expect(output).toContain("<number> 3");
61
+
62
+ // Ensure there is NO tag next to strings (because they match the list type)
63
+ expect(output).not.toContain('<string> "a"');
64
+
65
+ const expected = '<tests:[string]>(["a","b","c",<number> 3])';
66
+ assertRoundtrip(node, expected, false);
67
+ });
68
+
69
+ it('should generate tags for explicit any list due to primitive mismatch', () => {
70
+ /**
71
+ * Tests the scenario where a list is defined as [any].
72
+ * * Behavior:
73
+ * 1. 'any' is parsed as a PRIMITIVE type named "any".
74
+ * 2. The Decoder updates the schema based on the first element found ("a" -> string).
75
+ * 3. Because "string" != "number" (for value 3), the Encoder sees a mismatch and adds explicit tags.
76
+ */
77
+
78
+ // 1. Input in AID format
79
+ const aidText = `
80
+ <tests: [any]>
81
+ (
82
+ ["a", "b", "c", 3]
83
+ )
84
+ `;
85
+
86
+ // 2. Decode
87
+ const result = decode(aidText, { debug: false });
88
+ const node = result.node;
89
+ expect(result.errors).toHaveLength(0);
90
+
91
+ // 3. Verify Internal State
92
+ const testsNode = node.fields["tests"];
93
+
94
+ // Verify the list definition
95
+ const elementSchema = testsNode.schema.element;
96
+ expect(elementSchema?.kind).toBe(SchemaKind.PRIMITIVE);
97
+
98
+ // Decoder logic updates "any" element schema based on the first item found
99
+ expect(elementSchema?.typeName).toBe("string");
100
+
101
+ // Verify the actual elements
102
+ expect(testsNode.elements[0].schema.typeName).toBe("string");
103
+ expect(testsNode.elements[3].schema.typeName).toBe("number");
104
+
105
+ // 4. Encode
106
+ const expected = '<tests:[string]>(["a","b","c",<number> 3])';
107
+ assertRoundtrip(node, expected, false);
108
+ });
109
+
110
+ it('should handle inference happy path', () => {
111
+ /**
112
+ * Theory: If a list has no type (or [any]), the first element ("a")
113
+ * should refine the list schema to [string].
114
+ */
115
+ const dataStr = '["a", "b"]'; // No header = SchemaKind.ANY
116
+
117
+ const result = decode(dataStr, { debug: false });
118
+ const node = result.node;
119
+ expect(result.errors).toHaveLength(0);
120
+
121
+ expect(node.isList).toBe(true);
122
+ expect(node.schema.element?.typeName).toBe("string"); // Inferred!
123
+
124
+ const expected = '<[string]>["a","b"]';
125
+ assertRoundtrip(node, expected, false);
126
+ });
127
+
128
+ it('should handle inference mismatch (string vs number)', () => {
129
+ /**
130
+ * Theory: First element "a" sets list to [string].
131
+ * The number 3 is mismatch and gets tagged.
132
+ */
133
+ const dataStr = '["a", 3]';
134
+
135
+ const result = decode(dataStr, { debug: false });
136
+ const node = result.node;
137
+ expect(result.errors).toHaveLength(0);
138
+
139
+ // The list should have become [string] due to "a"
140
+ expect(node.schema.element?.typeName).toBe("string");
141
+
142
+ const expected = '<[string]>["a",<number> 3]';
143
+ assertRoundtrip(node, expected, false);
144
+ });
145
+
146
+ it('should handle inference mismatch (number vs string)', () => {
147
+ /**
148
+ * Theory: First element 3 sets list to [number].
149
+ * The string "a" is mismatch and gets tagged.
150
+ */
151
+ const dataStr = '[3, "a"]';
152
+
153
+ const result = decode(dataStr, { debug: false });
154
+ const node = result.node;
155
+ expect(result.errors).toHaveLength(0);
156
+
157
+ // The list should have become [number] due to 3
158
+ expect(node.schema.element?.typeName).toBe("number");
159
+
160
+ const expected = '<[number]>[3,<string> "a"]';
161
+ assertRoundtrip(node, expected, false);
162
+ });
163
+
164
+ it('should fix schema crash on override logic', () => {
165
+ /**
166
+ * Theory: We expect a String, but we get a List.
167
+ * This triggers 'needs_override' logic in the Encoder.
168
+ */
169
+ // Header says 'test' is a string, but body has a list
170
+ const aidText = `
171
+ <test: string>
172
+ (
173
+ ["a", "b"]
174
+ )
175
+ `;
176
+
177
+ const result = decode(aidText, { debug: false });
178
+ const node = result.node;
179
+ expect(result.errors).toHaveLength(0);
180
+
181
+ // The Encoder sees a List Node vs String Schema -> Mismatch -> Adds Tag
182
+ const expected = '<test:string>(<[string]> ["a","b"])';
183
+ assertRoundtrip(node, expected, false);
184
+ });
185
+
186
+ it('should handle primitive list with implicit output', () => {
187
+ const aidText = `
188
+ <ab>
189
+ {
190
+ ab: ["a", "b", "c", 3]
191
+ }
192
+ `;
193
+
194
+ const result = decode(aidText, { debug: false });
195
+ const node = result.node;
196
+ expect(result.errors).toHaveLength(0);
197
+
198
+ const expected = '<ab:[string]>(["a","b","c",<number> 3])';
199
+ assertRoundtrip(node, expected, false);
200
+ });
201
+
202
+ it('should handle simple mixed types', () => {
203
+ const aidText = '["a", "b", "c", 3]';
204
+
205
+ const result = decode(aidText, { debug: false });
206
+ const node = result.node;
207
+ expect(result.errors).toHaveLength(0);
208
+
209
+ const expected = '<[string]>["a","b","c",<number> 3]';
210
+ assertRoundtrip(node, expected, false);
211
+ });
212
+
213
+ it('should handle inner list types', () => {
214
+ // <[[int]]>[[2,3,4],[5,6,7]]
215
+ const aidText = '<[[int]]>[[2,3,4],[5,6,7]]';
216
+
217
+ const result = decode(aidText, { debug: false });
218
+ const node = result.node;
219
+ expect(result.errors).toHaveLength(0);
220
+
221
+ // 'int' normalizes to 'number' in the TS implementation
222
+ const expected = '<[[number]]>[[2,3,4],[5,6,7]]';
223
+ assertRoundtrip(node, expected, false);
224
+ });
225
+ });
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { decode } from '../src/index';
3
+ import { assertRoundtrip } from './utils';
4
+
5
+ describe('AI Data Record Handling', () => {
6
+
7
+ // ==============================================================================
8
+ // 1. SCHEMA DEFINITION META (Types defined in < ... >)
9
+ // ==============================================================================
10
+
11
+ it('should handle different type in record', () => {
12
+ const aidText = `
13
+ <
14
+ id: number
15
+ >
16
+ ( ["text"] )
17
+ `;
18
+
19
+ const result = decode(aidText, { debug: false });
20
+ const node = result.node;
21
+ expect(result.errors).toHaveLength(0);
22
+
23
+ // 1. Check Record Meta (Outer)
24
+ expect(node.isRecord).toBe(true);
25
+
26
+ const expected = '<id:number>(<[string]> ["text"])';
27
+ assertRoundtrip(node, expected, false);
28
+ });
29
+
30
+ it('should handle simple types', () => {
31
+ // Input is a raw AID format string representing a dictionary
32
+ const aidText = '{ a:"a", b:"b", c:"c", d: 3 }';
33
+
34
+ // The parser infers the schema from the keys/values.
35
+ // The encoder then outputs the inferred schema and converts the named record
36
+ // to a positional one because a schema exists.
37
+ const expected = '<a:string,b:string,c:string,d:number>("a","b","c",3)';
38
+
39
+ assertRoundtrip(aidText, expected, false);
40
+ });
41
+
42
+ it('should handle record named type mismatch', () => {
43
+ /**
44
+ * Tests a scenario where a record field has a defined type (e.g., string),
45
+ * but the actual value inside is of a different type (e.g., int).
46
+ *
47
+ * This ensures that the encodeRecord method uses applyTypeTag correctly.
48
+ *
49
+ * Schema: <tests: string>
50
+ * Data: { tests: 3 }
51
+ * Expected Output: ... (<number> 3)
52
+ */
53
+ const aidText = `
54
+ <tests: string>
55
+ {
56
+ tests: 3
57
+ }
58
+ `;
59
+
60
+ const expected = '<tests:string>(<number> 3)';
61
+ assertRoundtrip(aidText, expected, false);
62
+ });
63
+
64
+ it('should handle record positional type mismatch', () => {
65
+ /**
66
+ * Tests a scenario where a positional record field has a defined type (e.g., string),
67
+ * but the actual value inside is of a different type (e.g., int).
68
+ *
69
+ * Schema: <tests: string>
70
+ * Data: (3)
71
+ * Expected Output: ... (<number> 3)
72
+ */
73
+ const aidText = `
74
+ <tests: string>
75
+ (3)
76
+ `;
77
+
78
+ const expected = '<tests:string>(<number> 3)';
79
+ assertRoundtrip(aidText, expected, false);
80
+ });
81
+
82
+ });