@arkadia/data 0.1.7 → 0.1.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.
Files changed (54) hide show
  1. package/.prettierrc +8 -0
  2. package/README.md +159 -112
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js.map +1 -1
  5. package/dist/core/Decoder.d.ts.map +1 -1
  6. package/dist/core/Decoder.js +123 -97
  7. package/dist/core/Decoder.js.map +1 -1
  8. package/dist/core/Encoder.d.ts +1 -2
  9. package/dist/core/Encoder.d.ts.map +1 -1
  10. package/dist/core/Encoder.js +74 -76
  11. package/dist/core/Encoder.js.map +1 -1
  12. package/dist/core/Parser.d.ts +1 -1
  13. package/dist/core/Parser.d.ts.map +1 -1
  14. package/dist/core/Parser.js +11 -11
  15. package/dist/core/Parser.js.map +1 -1
  16. package/dist/index.d.ts +4 -4
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +7 -8
  19. package/dist/index.js.map +1 -1
  20. package/dist/models/Meta.d.ts +3 -2
  21. package/dist/models/Meta.d.ts.map +1 -1
  22. package/dist/models/Meta.js +3 -3
  23. package/dist/models/Meta.js.map +1 -1
  24. package/dist/models/Node.d.ts +4 -3
  25. package/dist/models/Node.d.ts.map +1 -1
  26. package/dist/models/Node.js +29 -23
  27. package/dist/models/Node.js.map +1 -1
  28. package/dist/models/Schema.d.ts.map +1 -1
  29. package/dist/models/Schema.js +27 -21
  30. package/dist/models/Schema.js.map +1 -1
  31. package/eslint.config.mjs +42 -0
  32. package/package.json +11 -1
  33. package/scripts/verify-build.js +95 -92
  34. package/src/config.ts +75 -75
  35. package/src/core/Decoder.ts +984 -922
  36. package/src/core/Encoder.ts +364 -371
  37. package/src/core/Parser.ts +112 -112
  38. package/src/index.ts +18 -20
  39. package/src/models/Meta.ts +107 -107
  40. package/src/models/Node.ts +190 -185
  41. package/src/models/Schema.ts +198 -193
  42. package/tests/00.meta.test.ts +19 -25
  43. package/tests/00.node.test.ts +40 -48
  44. package/tests/00.primitive.test.ts +121 -95
  45. package/tests/00.schema.test.ts +28 -35
  46. package/tests/01.schema.test.ts +42 -52
  47. package/tests/02.data.test.ts +69 -75
  48. package/tests/03.errors.test.ts +53 -55
  49. package/tests/04.list.test.ts +192 -193
  50. package/tests/05.record.test.ts +54 -56
  51. package/tests/06.meta.test.ts +393 -389
  52. package/tests/utils.ts +47 -44
  53. package/tsconfig.json +27 -29
  54. package/vitest.config.ts +1 -1
@@ -6,66 +6,64 @@ import { encode, decode } from '../src/index';
6
6
  // ==================================================================================
7
7
 
8
8
  describe('A Data Error Handling', () => {
9
+ it('should report errors on unstripped ANSI codes', () => {
10
+ /**
11
+ * Validates that the Decoder handles ANSI codes gracefully (by reporting errors)
12
+ * if they are not stripped out before decoding.
13
+ */
14
+ const originalData = [
15
+ { id: 1, active: true },
16
+ { id: 2, active: false },
17
+ ];
9
18
 
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 }];
19
+ // 1. Encode WITH colors
20
+ // Note: We use camelCase keys for config in TypeScript
21
+ const encodedText = encode(originalData, {
22
+ compact: true,
23
+ includeSchema: true,
24
+ colorize: true,
25
+ });
16
26
 
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
+ // 2. Decode WITHOUT stripping colors (simulate user error)
28
+ // Pass removeAnsiColors: false explicitely
29
+ const res = decode(encodedText, { removeAnsiColors: false, debug: false });
27
30
 
28
- // 2. Decode WITHOUT stripping colors (simulate user error)
29
- // Pass removeAnsiColors: false explicitely
30
- const res = decode(encodedText, { removeAnsiColors: false, debug: false });
31
+ // 3. Assertions
32
+ expect(res.errors.length).toBeGreaterThan(0); // Decoder should report errors on raw ANSI codes
31
33
 
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
- });
34
+ // Optional: Verify error content
35
+ const messages = res.errors.map((e) => e.message);
36
+ const hasUnexpectedChar = messages.some((m) => m.includes('Unexpected character'));
37
+ expect(hasUnexpectedChar).toBe(true);
38
+ });
40
39
 
41
- // ==================================================================================
42
- // 5. PARSING ERROR SCENARIOS
43
- // ==================================================================================
40
+ // ==================================================================================
41
+ // 5. PARSING ERROR SCENARIOS
42
+ // ==================================================================================
44
43
 
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
- });
44
+ it('should fail on unclosed list', () => {
45
+ /**
46
+ * Ensures parsing fails for malformed lists.
47
+ */
48
+ const text = '[1, 2, 3'; // Missing closing bracket
49
+ const res = decode(text, { debug: false });
59
50
 
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
- });
51
+ expect(res.errors.length).toBeGreaterThan(0);
52
+
53
+ const msg = res.errors[0].message;
54
+ // Check for typical error messages related to missing tokens or EOF
55
+ const isExpectedError = msg.includes('Expected') || msg.includes('got') || msg.includes('EOF');
56
+ expect(isExpectedError).toBe(true);
57
+ });
58
+
59
+ it('should fail on unexpected character', () => {
60
+ /**
61
+ * Ensures parsing fails for illegal characters.
62
+ */
63
+ const text = '(1, ?)';
64
+ const res = decode(text, { debug: false });
70
65
 
71
- });
66
+ expect(res.errors.length).toBeGreaterThan(0);
67
+ expect(res.errors[0].message).toContain('Unexpected character');
68
+ });
69
+ });
@@ -3,223 +3,222 @@ import { encode, decode, parse, SchemaKind } from '../src/index';
3
3
  import { assertRoundtrip } from './utils';
4
4
 
5
5
  describe('AK 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 AKD format
79
- const akdText = `
6
+ it('should decode list of primitives', () => {
7
+ /**
8
+ * Validates simple lists.
9
+ */
10
+ const text = '[1, 2, 3]';
11
+ const expected = '<[number]>[1,2,3]';
12
+
13
+ const res = decode(text, { debug: false });
14
+ expect(res.errors).toHaveLength(0);
15
+
16
+ const node = res.node;
17
+ // Check elements (Node logic stores simple lists in elements)
18
+ const values = node.elements.map((el) => el.value);
19
+ expect(values).toEqual([1, 2, 3]);
20
+
21
+ assertRoundtrip(node, expected, false);
22
+ });
23
+
24
+ it('should infer mixed list type from first element', () => {
25
+ /**
26
+ * Tests if the list type is inferred based on the first element ("a" -> string).
27
+ * As a result, the number 3 (int) should be treated as a mismatch and marked with the <number> tag.
28
+ */
29
+ // 1. Input data: List starts with strings but has an int at the end
30
+ const data = { tests: ['a', 'b', 'c', 3] };
31
+
32
+ // 2. Encoding (inference happens here in Parser.ts)
33
+ const node = parse(data);
34
+
35
+ expect(node.schema).not.toBeNull();
36
+ expect(node.schema.isRecord).toBe(true);
37
+ expect(node.fields).toHaveProperty('tests');
38
+
39
+ const testsNode = node.fields['tests'];
40
+ expect(testsNode.isList).toBe(true);
41
+ expect(testsNode.schema).not.toBeNull();
42
+ expect(testsNode.schema.element).not.toBeNull();
43
+
44
+ expect(testsNode.schema.element?.kind).toBe(SchemaKind.PRIMITIVE);
45
+ expect(testsNode.schema.element?.typeName).toBe('string'); // Inferred from first element "a"
46
+
47
+ expect(testsNode.elements).toHaveLength(4);
48
+
49
+ const output = encode(data, { compact: true, colorize: false });
50
+
51
+ // We expect the list NOT to be [any], but [string] (implied or explicit),
52
+ // so strings will be "clean", and the number will get a tag.
53
+
54
+ // Check if 'a' is treated normally (as a string in a string list)
55
+ expect(output).toContain('"a"');
56
+
57
+ // KEY: Check if 3 got a tag because it doesn't match the inferred String type
58
+ // TS Encoder outputs <number>
59
+ expect(output).toContain('<number> 3');
60
+
61
+ // Ensure there is NO tag next to strings (because they match the list type)
62
+ expect(output).not.toContain('<string> "a"');
63
+
64
+ const expected = '<tests:[string]>(["a","b","c",<number> 3])';
65
+ assertRoundtrip(node, expected, false);
66
+ });
67
+
68
+ it('should generate tags for explicit any list due to primitive mismatch', () => {
69
+ /**
70
+ * Tests the scenario where a list is defined as [any].
71
+ * * Behavior:
72
+ * 1. 'any' is parsed as a PRIMITIVE type named "any".
73
+ * 2. The Decoder updates the schema based on the first element found ("a" -> string).
74
+ * 3. Because "string" != "number" (for value 3), the Encoder sees a mismatch and adds explicit tags.
75
+ */
76
+
77
+ // 1. Input in AKD format
78
+ const akdText = `
80
79
  <tests: [any]>
81
80
  (
82
81
  ["a", "b", "c", 3]
83
82
  )
84
83
  `;
85
84
 
86
- // 2. Decode
87
- const result = decode(akdText, { 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 akdText = `
85
+ // 2. Decode
86
+ const result = decode(akdText, { debug: false });
87
+ const node = result.node;
88
+ expect(result.errors).toHaveLength(0);
89
+
90
+ // 3. Verify Internal State
91
+ const testsNode = node.fields['tests'];
92
+
93
+ // Verify the list definition
94
+ const elementSchema = testsNode.schema.element;
95
+ expect(elementSchema?.kind).toBe(SchemaKind.PRIMITIVE);
96
+
97
+ // Decoder logic updates "any" element schema based on the first item found
98
+ expect(elementSchema?.typeName).toBe('string');
99
+
100
+ // Verify the actual elements
101
+ expect(testsNode.elements[0].schema.typeName).toBe('string');
102
+ expect(testsNode.elements[3].schema.typeName).toBe('number');
103
+
104
+ // 4. Encode
105
+ const expected = '<tests:[string]>(["a","b","c",<number> 3])';
106
+ assertRoundtrip(node, expected, false);
107
+ });
108
+
109
+ it('should handle inference happy path', () => {
110
+ /**
111
+ * Theory: If a list has no type (or [any]), the first element ("a")
112
+ * should refine the list schema to [string].
113
+ */
114
+ const dataStr = '["a", "b"]'; // No header = SchemaKind.ANY
115
+
116
+ const result = decode(dataStr, { debug: false });
117
+ const node = result.node;
118
+ expect(result.errors).toHaveLength(0);
119
+
120
+ expect(node.isList).toBe(true);
121
+ expect(node.schema.element?.typeName).toBe('string'); // Inferred!
122
+
123
+ const expected = '<[string]>["a","b"]';
124
+ assertRoundtrip(node, expected, false);
125
+ });
126
+
127
+ it('should handle inference mismatch (string vs number)', () => {
128
+ /**
129
+ * Theory: First element "a" sets list to [string].
130
+ * The number 3 is mismatch and gets tagged.
131
+ */
132
+ const dataStr = '["a", 3]';
133
+
134
+ const result = decode(dataStr, { debug: false });
135
+ const node = result.node;
136
+ expect(result.errors).toHaveLength(0);
137
+
138
+ // The list should have become [string] due to "a"
139
+ expect(node.schema.element?.typeName).toBe('string');
140
+
141
+ const expected = '<[string]>["a",<number> 3]';
142
+ assertRoundtrip(node, expected, false);
143
+ });
144
+
145
+ it('should handle inference mismatch (number vs string)', () => {
146
+ /**
147
+ * Theory: First element 3 sets list to [number].
148
+ * The string "a" is mismatch and gets tagged.
149
+ */
150
+ const dataStr = '[3, "a"]';
151
+
152
+ const result = decode(dataStr, { debug: false });
153
+ const node = result.node;
154
+ expect(result.errors).toHaveLength(0);
155
+
156
+ // The list should have become [number] due to 3
157
+ expect(node.schema.element?.typeName).toBe('number');
158
+
159
+ const expected = '<[number]>[3,<string> "a"]';
160
+ assertRoundtrip(node, expected, false);
161
+ });
162
+
163
+ it('should fix schema crash on override logic', () => {
164
+ /**
165
+ * Theory: We expect a String, but we get a List.
166
+ * This triggers 'needs_override' logic in the Encoder.
167
+ */
168
+ // Header says 'test' is a string, but body has a list
169
+ const akdText = `
171
170
  <test: string>
172
171
  (
173
172
  ["a", "b"]
174
173
  )
175
174
  `;
176
175
 
177
- const result = decode(akdText, { debug: false });
178
- const node = result.node;
179
- expect(result.errors).toHaveLength(0);
176
+ const result = decode(akdText, { debug: false });
177
+ const node = result.node;
178
+ expect(result.errors).toHaveLength(0);
180
179
 
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
- });
180
+ // The Encoder sees a List Node vs String Schema -> Mismatch -> Adds Tag
181
+ const expected = '<test:string>(<[string]> ["a","b"])';
182
+ assertRoundtrip(node, expected, false);
183
+ });
185
184
 
186
- it('should handle primitive list with implicit output', () => {
187
- const akdText = `
185
+ it('should handle primitive list with implicit output', () => {
186
+ const akdText = `
188
187
  <ab>
189
188
  {
190
189
  ab: ["a", "b", "c", 3]
191
190
  }
192
191
  `;
193
192
 
194
- const result = decode(akdText, { debug: false });
195
- const node = result.node;
196
- expect(result.errors).toHaveLength(0);
193
+ const result = decode(akdText, { debug: false });
194
+ const node = result.node;
195
+ expect(result.errors).toHaveLength(0);
197
196
 
198
- const expected = '<ab:[string]>(["a","b","c",<number> 3])';
199
- assertRoundtrip(node, expected, false);
200
- });
197
+ const expected = '<ab:[string]>(["a","b","c",<number> 3])';
198
+ assertRoundtrip(node, expected, false);
199
+ });
201
200
 
202
- it('should handle simple mixed types', () => {
203
- const akdText = '["a", "b", "c", 3]';
201
+ it('should handle simple mixed types', () => {
202
+ const akdText = '["a", "b", "c", 3]';
204
203
 
205
- const result = decode(akdText, { debug: false });
206
- const node = result.node;
207
- expect(result.errors).toHaveLength(0);
204
+ const result = decode(akdText, { debug: false });
205
+ const node = result.node;
206
+ expect(result.errors).toHaveLength(0);
208
207
 
209
- const expected = '<[string]>["a","b","c",<number> 3]';
210
- assertRoundtrip(node, expected, false);
211
- });
208
+ const expected = '<[string]>["a","b","c",<number> 3]';
209
+ assertRoundtrip(node, expected, false);
210
+ });
212
211
 
213
- it('should handle inner list types', () => {
214
- // <[[int]]>[[2,3,4],[5,6,7]]
215
- const akdText = '<[[int]]>[[2,3,4],[5,6,7]]';
212
+ it('should handle inner list types', () => {
213
+ // <[[int]]>[[2,3,4],[5,6,7]]
214
+ const akdText = '<[[int]]>[[2,3,4],[5,6,7]]';
216
215
 
217
- const result = decode(akdText, { debug: false });
218
- const node = result.node;
219
- expect(result.errors).toHaveLength(0);
216
+ const result = decode(akdText, { debug: false });
217
+ const node = result.node;
218
+ expect(result.errors).toHaveLength(0);
220
219
 
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
- });
220
+ // 'int' normalizes to 'number' in the TS implementation
221
+ const expected = '<[[number]]>[[2,3,4],[5,6,7]]';
222
+ assertRoundtrip(node, expected, false);
223
+ });
224
+ });