@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
@@ -1,5 +1,5 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { encode, decode, MetaInfo, parse } from '../src/index';
1
+ import { describe, expect, it } from 'vitest';
2
+ import { decode, encode } from '../src/index';
3
3
  import { assertRoundtrip } from './utils';
4
4
 
5
5
  // ==================================================================================
@@ -7,102 +7,128 @@ import { assertRoundtrip } from './utils';
7
7
  // ==================================================================================
8
8
 
9
9
  describe('AK Data Primitives', () => {
10
- it('should encode raw object', () => {
11
- const input = { foo: "bar" };
12
- const expected = '<foo:string>("bar")';
13
- assertRoundtrip(input, expected);
14
- });
10
+ it('should encode raw object', () => {
11
+ const input = { foo: 'bar' };
12
+ const expected = '<foo:string>("bar")';
13
+ assertRoundtrip(input, expected);
14
+ });
15
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
- });
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 });
39
29
 
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
- });
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);
54
33
 
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
- });
34
+ // 2. ENCODE
35
+ const encodedStr = encode(res.node, { colorize: false, compact: true }).trim();
72
36
 
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
- });
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
+ ];
92
46
 
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
- );
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');
107
52
  });
108
- });
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(node, '<id:number,name:string>(1,"Test")', false);
67
+ });
68
+
69
+ it('should decode positional record', () => {
70
+ const text = '(10, "Alice")';
71
+ const res = decode(text, { debug: false });
72
+ expect(res.errors).toHaveLength(0);
73
+
74
+ const node = res.node;
75
+
76
+ expect(node.isRecord).toBe(true);
77
+
78
+ // Verify if the parser mapped positional fields to _0, _1
79
+ expect(node.fields['_0'].value).toBe(10);
80
+ expect(node.fields['_1'].value).toBe('Alice');
81
+
82
+ assertRoundtrip(node, '<_0:number,_1:string>(10,"Alice")', false);
83
+ });
84
+
85
+ it('should decode raw string', () => {
86
+ const text = '{color: red, status: active}';
87
+ const res = decode(text, { debug: false });
88
+ expect(res.errors).toHaveLength(0);
89
+
90
+ const node = res.node;
91
+ expect(node.fields['color'].value).toBe('red');
92
+ expect(node.fields['status'].value).toBe('active');
93
+
94
+ assertRoundtrip(node, '<color:string,status:string>("red","active")', false);
95
+ });
96
+ });
97
+
98
+ describe('String Escaping', () => {
99
+ it('should decode string with escaped quotes', () => {
100
+ // Input: { text: "say \"hello\"" }
101
+ // In-memory value: say "hello"
102
+ // Note: In JS string literals, we need extra backslashes to represent a literal backslash.
103
+ const text = '{ text: "say \\"hello\\"" }';
104
+
105
+ const res = decode(text, { debug: false });
106
+ expect(res.errors).toHaveLength(0);
107
+
108
+ const node = res.node;
109
+ // Check in-memory value
110
+ expect(node.fields['text'].value).toBe('say "hello"');
111
+
112
+ // Roundtrip check:
113
+ // Encoder must add backslashes before quotes: "say \"hello\""
114
+ assertRoundtrip(node, '<text:string>("say \\"hello\\"")', false);
115
+ });
116
+
117
+ it('should decode string with escaped backslashes', () => {
118
+ // Input: { path: "C:\\Program Files" }
119
+ // In-memory value: C:\Program Files
120
+ // Note: "C:\\\\Program Files" in JS literal becomes C:\\Program Files in the string
121
+ const text = '{ path: "C:\\\\Program Files" }';
122
+
123
+ const res = decode(text, { debug: false });
124
+ expect(res.errors).toHaveLength(0);
125
+
126
+ const node = res.node;
127
+ // Check in-memory value (single backslash)
128
+ expect(node.fields['path'].value).toBe('C:\\Program Files');
129
+
130
+ // Roundtrip check:
131
+ // Encoder must escape the backslash: "C:\\Program Files"
132
+ assertRoundtrip(node, '<path:string>("C:\\\\Program Files")', false);
133
+ });
134
+ });
@@ -2,40 +2,33 @@ import { describe, it, expect } from 'vitest';
2
2
  import { Schema, SchemaKind } from '../src/index';
3
3
 
4
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);
5
+ it('should encode raw object', () => {
6
+ const schema = new Schema(SchemaKind.DICT, {
7
+ name: 'TestSchema',
8
+ comments: ['This is a comment'],
9
+ attr: { foo: 'bar' },
10
+ tags: ['tag1', 'tag2'],
11
+ required: true,
40
12
  });
13
+ const expected =
14
+ '<Schema(DICT) name="TestSchema" !required attr=["foo"] tags=[tag1, tag2] comments=1>';
15
+ const expected_val = {
16
+ comments: ['This is a comment'],
17
+ attr: {
18
+ foo: 'bar',
19
+ },
20
+ tags: ['tag1', 'tag2'],
21
+ _fieldsList: [],
22
+ _fieldsMap: {},
23
+ kind: 'DICT',
24
+ typeName: 'any',
25
+ name: 'TestSchema',
26
+ element: null,
27
+ key: null,
28
+ value: null,
29
+ required: true,
30
+ };
31
+ expect(schema).toMatchObject(expected_val);
32
+ expect(schema.toString()).toBe(expected);
33
+ });
41
34
  });
@@ -7,64 +7,54 @@ import { assertRoundtrip } from './utils';
7
7
  // ==================================================================================
8
8
 
9
9
  describe('AK 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 = `
10
+ it('should handle schema definition and usage', () => {
11
+ /**
12
+ * Validates that a type defined with @Type<...> is correctly applied
13
+ * to the following value.
14
+ */
15
+ // Define schema first, then use it explicitly
16
+ const fullText = '@User<id:int, name:string> @User(1, "Admin")';
17
+ const res = decode(fullText, { debug: false });
18
+
19
+ expect(res.errors).toHaveLength(0);
20
+
21
+ const node = res.node;
22
+ // Check if schema is linked correctly
23
+ expect(node.schema).not.toBeNull();
24
+ expect(node.schema?.typeName).toBe('User');
25
+
26
+ // Since we have a schema, positional arguments should be mapped to fields
27
+ // Check by key (Decoder maps positional to fields if schema exists)
28
+ expect(node.fields['id'].value).toBe(1);
29
+ expect(node.fields['name'].value).toBe('Admin');
30
+
31
+ assertRoundtrip(node, '@User<id:number,name:string>(1,"Admin")', false);
32
+ });
33
+
34
+ it('should handle nested schema structure', () => {
35
+ /**
36
+ * Validates nested structural types.
37
+ */
38
+ // We define Profile, then User uses it.
39
+ const text = `
45
40
  @Profile<level:int>
46
41
  @User<id:int, profile: @Profile>
47
42
  @User(1, {level: 99})
48
43
  `;
49
-
50
- const res = decode(text, { debug: false });
51
44
 
52
- expect(res.errors).toHaveLength(0);
53
- const node = res.node;
45
+ const res = decode(text, { debug: false });
54
46
 
55
- // id should be 1
56
- expect(node.fields["id"].value).toBe(1);
47
+ expect(res.errors).toHaveLength(0);
48
+ const node = res.node;
57
49
 
58
- // profile should be a node
59
- const profileNode = node.fields["profile"];
60
- expect(profileNode).toBeDefined();
61
- expect(profileNode.fields["level"].value).toBe(99);
50
+ // id should be 1
51
+ expect(node.fields['id'].value).toBe(1);
62
52
 
63
- assertRoundtrip(
64
- node,
65
- "@User<id:number,profile:@Profile<level:number>>(1,(99))",
66
- false
67
- );
68
- });
53
+ // profile should be a node
54
+ const profileNode = node.fields['profile'];
55
+ expect(profileNode).toBeDefined();
56
+ expect(profileNode.fields['level'].value).toBe(99);
69
57
 
70
- });
58
+ assertRoundtrip(node, '@User<id:number,profile:@Profile<level:number>>(1,(99))', false);
59
+ });
60
+ });
@@ -7,83 +7,77 @@ import { assertRoundtrip } from './utils';
7
7
  // ==================================================================================
8
8
 
9
9
  describe('AK Data Encoding', () => {
10
+ it('should encode simple dict', () => {
11
+ /**
12
+ * Validates encoding a JS Object to AI.DATA format.
13
+ */
14
+ const data = { x: 10, y: 20 };
10
15
 
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
- });
16
+ const result = encode(data, { compact: true });
17
+ const expected = '<x:number,y:number>(10,20)';
28
18
 
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
- });
19
+ expect(result).toBe(expected);
20
+
21
+ assertRoundtrip(result, expected, false);
22
+ });
23
+
24
+ it('should encode list of objects', () => {
25
+ /**
26
+ * Validates encoding a list of objects.
27
+ */
28
+ const data = [
29
+ { name: 'A', val: 1 },
30
+ { name: 'B', val: 2 },
31
+ ];
32
+
33
+ const result = encode(data, { compact: true, colorize: false });
34
+ const expected = '<[name:string,val:number]>[("A",1),("B",2)]';
35
+
36
+ expect(result).toBe(expected);
37
+
38
+ assertRoundtrip(result, expected, false);
39
+ });
40
+
41
+ it('should handle round trip consistency', () => {
42
+ /**
43
+ * Golden Test: Encode -> Decode -> Compare.
44
+ */
45
+ const originalData = [
46
+ { id: 1, active: true, tags: ['a', 'b'] },
47
+ { id: 2, active: false, tags: ['c'] },
48
+ ];
46
49
 
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);
50
+ const expected = '<[id:number,active:bool,tags:[string]]>[(1,true,["a","b"]),(2,false,["c"])]';
51
+
52
+ // 1. Encode
53
+ // Note: converted snake_case config keys to camelCase for TS
54
+ const encodedText = encode(originalData, {
55
+ compact: true,
56
+ includeSchema: true,
57
+ colorize: false,
87
58
  });
88
59
 
89
- });
60
+ expect(encodedText).toBe(expected);
61
+ assertRoundtrip(encodedText, expected, false);
62
+
63
+ // 2. Decode
64
+ const res = decode(encodedText, { debug: false });
65
+
66
+ // Optional: inspect schema structure
67
+
68
+ expect(res.errors).toHaveLength(0);
69
+
70
+ // 3. Convert back to JS object (equivalent to .dict() in Python)
71
+ const decodedData = res.node.dict();
72
+
73
+ // 4. Compare
74
+ expect(decodedData).toHaveLength(2);
75
+ expect(decodedData[0].id).toBe(1);
76
+ expect(decodedData[0].active).toBe(true);
77
+ expect(decodedData[0].tags).toEqual(['a', 'b']);
78
+ expect(decodedData[1].active).toBe(false);
79
+
80
+ // Deep equality check
81
+ expect(decodedData).toEqual(originalData);
82
+ });
83
+ });