@arkadia/data 0.1.7 → 0.1.8

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 +166 -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,31 +1,30 @@
1
- import { describe, it, expect } from 'vitest';
1
+ import { describe, expect, it } from 'vitest';
2
2
  import { decode, encode, Node, Schema, SchemaKind } from '../src/index';
3
3
  import { assertRoundtrip } from './utils';
4
4
 
5
5
  describe('AK Data Metadata (Meta)', () => {
6
+ // ==================================================================================
7
+ // 2. SCHEMA DEFINITION & TYPING
8
+ // ==================================================================================
6
9
 
7
- // ==================================================================================
8
- // 2. SCHEMA DEFINITION & TYPING
9
- // ==================================================================================
10
-
11
- it('should handle comments', () => {
12
- /** Validates that comments /.../ are ignored or handled. */
13
- const text = '@User<id:int /*primary key*/, name:string> @User(5, "Bob")';
14
- // Note: int normalizes to number in TS
15
- const expected = '@User</*primary key*/ id:number,name:string>(5,"Bob")';
16
-
17
- const res = decode(text, { debug: false });
18
- expect(res.errors).toHaveLength(0);
19
-
20
- // If the parser attaches schema comments to fields correctly, good.
21
- // Here we just ensure data parsing works despite comments.
22
- expect(res.node.fields["id"].value).toBe(5);
23
-
24
- assertRoundtrip(text, expected, false);
25
- });
10
+ it('should handle comments', () => {
11
+ /** Validates that comments /.../ are ignored or handled. */
12
+ const text = '@User<id:int /*primary key*/, name:string> @User(5, "Bob")';
13
+ // Note: int normalizes to number in TS
14
+ const expected = '@User</*primary key*/ id:number,name:string>(5,"Bob")';
15
+
16
+ const res = decode(text, { debug: false });
17
+ expect(res.errors).toHaveLength(0);
18
+
19
+ // If the parser attaches schema comments to fields correctly, good.
20
+ // Here we just ensure data parsing works despite comments.
21
+ expect(res.node.fields['id'].value).toBe(5);
22
+
23
+ assertRoundtrip(text, expected, false);
24
+ });
26
25
 
27
- it('should handle meta header', () => {
28
- const akdText = `
26
+ it('should handle meta header', () => {
27
+ const akdText = `
29
28
  $a0=5
30
29
  <
31
30
  /* c1 */
@@ -34,13 +33,14 @@ describe('AK Data Metadata (Meta)', () => {
34
33
  >
35
34
  ($a6 /*a*/ 3)
36
35
  `;
37
-
38
- const expected = "<//*c0*/ $a0=5 $a1=true/ /*c1*/ /*c2*/ /*c3*/ $a2=2 $a3=3 a:number>(/*a*/ $a6=true 3)";
39
- assertRoundtrip(akdText, expected, false);
40
- });
41
36
 
42
- it('should handle mixed meta', () => {
43
- const akdText = `
37
+ const expected =
38
+ '<//*c0*/ $a0=5 $a1=true/ /*c1*/ /*c2*/ /*c3*/ $a2=2 $a3=3 a:number>(/*a*/ $a6=true 3)';
39
+ assertRoundtrip(akdText, expected, false);
40
+ });
41
+
42
+ it('should handle mixed meta', () => {
43
+ const akdText = `
44
44
  $attr=5
45
45
  <
46
46
  /* comm2 */
@@ -55,24 +55,25 @@ describe('AK Data Metadata (Meta)', () => {
55
55
  /*item2*/ {a:5},
56
56
  ]
57
57
  `;
58
-
59
- // Note: int -> number
60
- const expected = "<[//*comm2*/ /*comm1*/ $attr=5 $schema1=true/ a:number]>[//*meta for list*/ $attr=4/ (//*item1*/ $attr5=true/ $attr6=true 3),(//*item2*// 5)]";
61
- assertRoundtrip(akdText, expected, false);
62
- });
63
58
 
64
- // ==============================================================================
65
- // 1. SCHEMA DEFINITION META (Types defined in < ... >)
66
- // ==============================================================================
67
-
68
- it('should handle list schema with meta', () => {
69
- /**
70
- * Verifies that an empty schema definition < ... > correctly parses:
71
- * 1. Comments (including nested ones).
72
- * 2. Attributes ($key=val).
73
- * 3. Tags (#tag).
74
- */
75
- const akdText = `
59
+ // Note: int -> number
60
+ const expected =
61
+ '<[//*comm2*/ /*comm1*/ $attr=5 $schema1=true/ a:number]>[//*meta for list*/ $attr=4/ (//*item1*/ $attr5=true/ $attr6=true 3),(//*item2*// 5)]';
62
+ assertRoundtrip(akdText, expected, false);
63
+ });
64
+
65
+ // ==============================================================================
66
+ // 1. SCHEMA DEFINITION META (Types defined in < ... >)
67
+ // ==============================================================================
68
+
69
+ it('should handle list schema with meta', () => {
70
+ /**
71
+ * Verifies that an empty schema definition < ... > correctly parses:
72
+ * 1. Comments (including nested ones).
73
+ * 2. Attributes ($key=val).
74
+ * 3. Tags (#tag).
75
+ */
76
+ const akdText = `
76
77
  /* 0 */
77
78
  <
78
79
  /* commentm0 */ /* com1 /*com1.2*/ */
@@ -89,147 +90,149 @@ describe('AK Data Metadata (Meta)', () => {
89
90
  ]
90
91
  /* b */
91
92
  `;
92
-
93
- const result = decode(akdText, { debug: false });
94
- const node = result.node;
95
- const errors = result.errors;
96
-
97
- // 1. Assert no syntax errors
98
- expect(errors).toHaveLength(0);
99
-
100
- // 2. Check Schema Basics
101
- const schema = node.schema;
102
- expect(schema).not.toBeNull();
103
- expect(schema.kind).toBe(SchemaKind.LIST); // Default kind for <...>
104
-
105
- // 3. Verify Attributes ($key=val)
106
- // Parser casts to number if possible
107
- expect(schema.attr["listAttr"]).toBe("GlobalList");
108
- expect(schema.attr["b"]).toBe(4);
109
-
110
- // 4. Verify Tags (#tag)
111
- expect(schema.tags).toContain("tag");
112
- expect(schema.tags).toHaveLength(1);
113
-
114
- // 5. Verify Comments
115
- // We expect multiple comments to be collected
116
- expect(schema.comments.length).toBeGreaterThan(0);
117
- expect(schema.comments.some(c => c.includes("0"))).toBe(true);
118
-
119
- const expected = '<[//*0*/ $listAttr="GlobalList" $b=4 #tag/ number]>[//*a*/ /*b*/ $val=3 #tag1/ 1,2,3]';
120
- assertRoundtrip(akdText, expected, false);
93
+
94
+ const result = decode(akdText, { debug: false });
95
+ const node = result.node;
96
+ const errors = result.errors;
97
+
98
+ // 1. Assert no syntax errors
99
+ expect(errors).toHaveLength(0);
100
+
101
+ // 2. Check Schema Basics
102
+ const schema = node.schema;
103
+ expect(schema).not.toBeNull();
104
+ expect(schema.kind).toBe(SchemaKind.LIST); // Default kind for <...>
105
+
106
+ // 3. Verify Attributes ($key=val)
107
+ // Parser casts to number if possible
108
+ expect(schema.attr['listAttr']).toBe('GlobalList');
109
+ expect(schema.attr['b']).toBe(4);
110
+
111
+ // 4. Verify Tags (#tag)
112
+ expect(schema.tags).toContain('tag');
113
+ expect(schema.tags).toHaveLength(1);
114
+
115
+ // 5. Verify Comments
116
+ // We expect multiple comments to be collected
117
+ expect(schema.comments.length).toBeGreaterThan(0);
118
+ expect(schema.comments.some((c) => c.includes('0'))).toBe(true);
119
+
120
+ const expected =
121
+ '<[//*0*/ $listAttr="GlobalList" $b=4 #tag/ number]>[//*a*/ /*b*/ $val=3 #tag1/ 1,2,3]';
122
+ assertRoundtrip(akdText, expected, false);
123
+ });
124
+
125
+ it('should encode manual schema with meta', () => {
126
+ /**
127
+ * Verifies that a Schema with meta/comments is encoded correctly.
128
+ * Expected format: < / $attr=val #tag / any >
129
+ */
130
+
131
+ // 1. Prepare Schema manually
132
+ const schema = new Schema(SchemaKind.RECORD);
133
+ schema.comments = ['comment1', 'comment2'];
134
+ schema.attr = { key: 'value', count: 10 };
135
+ schema.tags = ['myTag'];
136
+
137
+ // Create a node using this schema
138
+ const node = new Node(schema, { value: null });
139
+ const expected = '<//*comment1*/ /*comment2*/ $key="value" $count=10 #myTag/ any>(null)';
140
+
141
+ assertRoundtrip(node, expected, false);
142
+ });
143
+
144
+ it('should round trip schema encode decode', () => {
145
+ /**
146
+ * Verifies that a Schema with meta/comments can be encoded to text
147
+ * and then decoded back, preserving all metadata (Round-Trip).
148
+ */
149
+
150
+ // 1. Prepare Schema manually
151
+ const originalSchema = new Schema(SchemaKind.RECORD);
152
+ originalSchema.comments = ['comment1', 'comment2'];
153
+ originalSchema.attr = { key: 'value', count: 10, isActive: true };
154
+ originalSchema.tags = ['myTag', 'urgent'];
155
+
156
+ // Create a node using this schema
157
+ const originalNode = new Node(originalSchema, { value: null });
158
+
159
+ // 2. Encode to String
160
+ // Important: We must enable include_comments to verify them after decoding
161
+ const encodedText = encode(originalNode, {
162
+ includeComments: true,
163
+ compact: true, // Test compact mode (one line)
164
+ colorize: false,
121
165
  });
122
166
 
123
- it('should encode manual schema with meta', () => {
124
- /**
125
- * Verifies that a Schema with meta/comments is encoded correctly.
126
- * Expected format: < / $attr=val #tag / any >
127
- */
128
-
129
- // 1. Prepare Schema manually
130
- const schema = new Schema(SchemaKind.RECORD);
131
- schema.comments = ["comment1", "comment2"];
132
- schema.attr = { "key": "value", "count": 10 };
133
- schema.tags = ["myTag"];
134
-
135
- // Create a node using this schema
136
- const node = new Node(schema, { value: null });
137
- const expected = '<//*comment1*/ /*comment2*/ $key="value" $count=10 #myTag/ any>(null)';
138
-
139
- assertRoundtrip(node, expected, false);
167
+ // 3. Decode back to Node
168
+ const result = decode(encodedText, { debug: false });
169
+ const decodedNode = result.node;
170
+ const errors = result.errors;
171
+
172
+ // 4. Verify No Errors
173
+ expect(errors).toHaveLength(0);
174
+ expect(decodedNode).not.toBeNull();
175
+
176
+ // 5. Verify Schema Integrity
177
+ const decodedSchema = decodedNode.schema;
178
+ expect(decodedSchema).not.toBeNull();
179
+ expect(decodedSchema.kind).toBe(SchemaKind.RECORD);
180
+
181
+ // 6. Verify Meta Data (Attributes)
182
+ expect(decodedSchema.attr['key']).toBe('value');
183
+ expect(decodedSchema.attr['count']).toBe(10);
184
+ expect(decodedSchema.attr['isActive']).toBe(true);
185
+
186
+ // 7. Verify Tags
187
+ expect(decodedSchema.tags).toContain('myTag');
188
+ expect(decodedSchema.tags).toContain('urgent');
189
+ expect(decodedSchema.tags).toHaveLength(2);
190
+
191
+ // 8. Verify Comments
192
+ expect(decodedSchema.comments).toHaveLength(2);
193
+ expect(decodedSchema.comments).toContain('comment1');
194
+ expect(decodedSchema.comments).toContain('comment2');
195
+
196
+ // Additional checks for configuration flags
197
+ const decodedTextClean = encode(decodedNode, {
198
+ compact: true,
199
+ includeMeta: false,
200
+ includeComments: false,
140
201
  });
141
202
 
142
- it('should round trip schema encode decode', () => {
143
- /**
144
- * Verifies that a Schema with meta/comments can be encoded to text
145
- * and then decoded back, preserving all metadata (Round-Trip).
146
- */
147
-
148
- // 1. Prepare Schema manually
149
- const originalSchema = new Schema(SchemaKind.RECORD);
150
- originalSchema.comments = ["comment1", "comment2"];
151
- originalSchema.attr = { "key": "value", "count": 10, "isActive": true };
152
- originalSchema.tags = ["myTag", "urgent"];
153
-
154
- // Create a node using this schema
155
- const originalNode = new Node(originalSchema, { value: null });
156
-
157
- // 2. Encode to String
158
- // Important: We must enable include_comments to verify them after decoding
159
- const encodedText = encode(originalNode, {
160
- includeComments: true,
161
- compact: true, // Test compact mode (one line)
162
- colorize: false
163
- });
164
-
165
- // 3. Decode back to Node
166
- const result = decode(encodedText, { debug: false });
167
- const decodedNode = result.node;
168
- const errors = result.errors;
169
-
170
- // 4. Verify No Errors
171
- expect(errors).toHaveLength(0);
172
- expect(decodedNode).not.toBeNull();
173
-
174
- // 5. Verify Schema Integrity
175
- const decodedSchema = decodedNode.schema;
176
- expect(decodedSchema).not.toBeNull();
177
- expect(decodedSchema.kind).toBe(SchemaKind.RECORD);
178
-
179
- // 6. Verify Meta Data (Attributes)
180
- expect(decodedSchema.attr["key"]).toBe("value");
181
- expect(decodedSchema.attr["count"]).toBe(10);
182
- expect(decodedSchema.attr["isActive"]).toBe(true);
183
-
184
- // 7. Verify Tags
185
- expect(decodedSchema.tags).toContain("myTag");
186
- expect(decodedSchema.tags).toContain("urgent");
187
- expect(decodedSchema.tags).toHaveLength(2);
188
-
189
- // 8. Verify Comments
190
- expect(decodedSchema.comments).toHaveLength(2);
191
- expect(decodedSchema.comments).toContain("comment1");
192
- expect(decodedSchema.comments).toContain("comment2");
193
-
194
- // Additional checks for configuration flags
195
- const decodedTextClean = encode(decodedNode, {
196
- compact: true,
197
- includeMeta: false,
198
- includeComments: false
199
- });
200
-
201
- // Assertions for No Meta
202
- expect(decodedTextClean).not.toContain("$key");
203
- expect(decodedTextClean).not.toContain("#myTag");
204
- expect(decodedTextClean).not.toContain("/"); // No meta block
205
- // Empty schema without meta encodes to <any> or similar
206
- expect(decodedTextClean).toContain("<any>");
207
-
208
- // B. Encode WITH Meta (includeMeta=true) but NO Type (includeType=false)
209
- const decodedTextWithMeta = encode(decodedNode, {
210
- compact: true,
211
- includeType: false,
212
- includeMeta: true,
213
- includeComments: false
214
- });
215
-
216
- // Assertions for With Meta
217
- expect(decodedTextWithMeta).toContain("$key=");
218
- expect(decodedTextWithMeta).toContain("#myTag");
219
- expect(decodedTextWithMeta).toContain("$count=10");
220
- expect(decodedTextWithMeta).toContain("/");
221
-
222
- const expected = '<//*comment1*/ /*comment2*/ $key="value" $count=10 $isActive=true #myTag #urgent/ any>(null)';
223
- assertRoundtrip(originalNode, expected, false);
203
+ // Assertions for No Meta
204
+ expect(decodedTextClean).not.toContain('$key');
205
+ expect(decodedTextClean).not.toContain('#myTag');
206
+ expect(decodedTextClean).not.toContain('/'); // No meta block
207
+ // Empty schema without meta encodes to <any> or similar
208
+ expect(decodedTextClean).toContain('<any>');
209
+
210
+ // B. Encode WITH Meta (includeMeta=true) but NO Type (includeType=false)
211
+ const decodedTextWithMeta = encode(decodedNode, {
212
+ compact: true,
213
+ includeType: false,
214
+ includeMeta: true,
215
+ includeComments: false,
224
216
  });
225
217
 
226
- it('should handle meta schema list vs element', () => {
227
- /**
228
- * Tests nested metadata within a type definition:
229
- * Outer: / $listAttr="GlobalList" / -> Applies to the entire List
230
- * Inner: / $elemAttr="InnerRecord" / -> Applies to the Element (Record) inside
231
- */
232
- const akdText = `
218
+ // Assertions for With Meta
219
+ expect(decodedTextWithMeta).toContain('$key=');
220
+ expect(decodedTextWithMeta).toContain('#myTag');
221
+ expect(decodedTextWithMeta).toContain('$count=10');
222
+ expect(decodedTextWithMeta).toContain('/');
223
+
224
+ const expected =
225
+ '<//*comment1*/ /*comment2*/ $key="value" $count=10 $isActive=true #myTag #urgent/ any>(null)';
226
+ assertRoundtrip(originalNode, expected, false);
227
+ });
228
+
229
+ it('should handle meta schema list vs element', () => {
230
+ /**
231
+ * Tests nested metadata within a type definition:
232
+ * Outer: / $listAttr="GlobalList" / -> Applies to the entire List
233
+ * Inner: / $elemAttr="InnerRecord" / -> Applies to the Element (Record) inside
234
+ */
235
+ const akdText = `
233
236
  <
234
237
  /* comm-header-0 */ /* comm-header-1 /* comm-header-1.1*/ */
235
238
  / $listAttr="GlobalList" $b=4 /*com-in*/ /
@@ -241,37 +244,38 @@ describe('AK Data Metadata (Meta)', () => {
241
244
  >
242
245
  [ /* comm-data-v1 */ (1) /* comm-data-v2 */ ]
243
246
  `;
244
-
245
- const results = decode(akdText, { debug: false });
246
- const node = results.node;
247
- const errors = results.errors;
248
-
249
- expect(errors).toHaveLength(0);
250
-
251
- // 1. Check List Meta (Outer)
252
- expect(node.isList).toBe(true);
253
- // Access attributes via .attr (Mixin)
254
- expect(node.schema.attr["listAttr"]).toBe("GlobalList");
255
- expect(node.schema.attr["elemAttr"]).toBe("InnerRecord");
256
-
257
- expect(node.schema.attr["b"]).toBe(4);
258
-
259
- // 2. Check Element Meta (Inner Record)
260
- const elemSchema = node.schema.element!;
261
- expect(elemSchema.kind).toBe(SchemaKind.RECORD);
262
- expect(elemSchema.attr).toStrictEqual({});
263
-
264
- // Check if element meta propagated to actual data elements (depends on implementation)
265
- // Usually schema meta is on schema, data meta is on data node.
266
- // Here we check the schema attached to the element node.
267
- expect(node.elements[0].schema.attr).toStrictEqual({});
268
-
269
- const expected = '<[//*com-in*/ /*comm-header-0*/ /*comm-header-1 /* comm-header-1.1*/*/ /*comm-after-header-0*/ /*comm-inside-header-0*/ $listAttr="GlobalList" $b=4 $elemAttr="InnerRecord" #elem0/ /*comm-inside-field-0*/ #elem1 id:number]>[(//*comm-data-v1*/ /*comm-data-v2*// 1)]';
270
- assertRoundtrip(akdText, expected, false);
271
- });
272
247
 
273
- it('should handle meta schema before fields', () => {
274
- const akdText = `
248
+ const results = decode(akdText, { debug: false });
249
+ const node = results.node;
250
+ const errors = results.errors;
251
+
252
+ expect(errors).toHaveLength(0);
253
+
254
+ // 1. Check List Meta (Outer)
255
+ expect(node.isList).toBe(true);
256
+ // Access attributes via .attr (Mixin)
257
+ expect(node.schema.attr['listAttr']).toBe('GlobalList');
258
+ expect(node.schema.attr['elemAttr']).toBe('InnerRecord');
259
+
260
+ expect(node.schema.attr['b']).toBe(4);
261
+
262
+ // 2. Check Element Meta (Inner Record)
263
+ const elemSchema = node.schema.element!;
264
+ expect(elemSchema.kind).toBe(SchemaKind.RECORD);
265
+ expect(elemSchema.attr).toStrictEqual({});
266
+
267
+ // Check if element meta propagated to actual data elements (depends on implementation)
268
+ // Usually schema meta is on schema, data meta is on data node.
269
+ // Here we check the schema attached to the element node.
270
+ expect(node.elements[0].schema.attr).toStrictEqual({});
271
+
272
+ const expected =
273
+ '<[//*com-in*/ /*comm-header-0*/ /*comm-header-1 /* comm-header-1.1*/*/ /*comm-after-header-0*/ /*comm-inside-header-0*/ $listAttr="GlobalList" $b=4 $elemAttr="InnerRecord" #elem0/ /*comm-inside-field-0*/ #elem1 id:number]>[(//*comm-data-v1*/ /*comm-data-v2*// 1)]';
274
+ assertRoundtrip(akdText, expected, false);
275
+ });
276
+
277
+ it('should handle meta schema before fields', () => {
278
+ const akdText = `
275
279
  <
276
280
  /* header-com-0 */
277
281
  / #tag_header /
@@ -284,21 +288,21 @@ describe('AK Data Metadata (Meta)', () => {
284
288
  /* comm-data-v3 */ #tag3 2 /* comm-data-v3 */ #tag4
285
289
  ]
286
290
  `;
287
-
288
- const results = decode(akdText, { debug: false });
289
- const node = results.node;
290
- expect(results.errors).toHaveLength(0);
291
291
 
292
- const expected = '<[/#tag_header/ number]>[/#tag_list/ /*comm-data-v1*/ #tag1 1,/*comm-data-v2*/ /*comm-data-v3*/ /*comm-data-v3*/ #tag2 #tag3 #tag4 2]';
293
- assertRoundtrip(akdText, expected, false);
294
- });
292
+ const results = decode(akdText, { debug: false });
293
+ expect(results.errors).toHaveLength(0);
295
294
 
296
- it('should warn on meta schema with implicit values', () => {
297
- /**
298
- * Tests handling of malformed meta blocks.
299
- * In the input: / listAttr="GlobalList" / is missing '$' prefix for attribute.
300
- */
301
- const akdText = `
295
+ const expected =
296
+ '<[/#tag_header/ number]>[/#tag_list/ /*comm-data-v1*/ #tag1 1,/*comm-data-v2*/ /*comm-data-v3*/ /*comm-data-v3*/ #tag2 #tag3 #tag4 2]';
297
+ assertRoundtrip(akdText, expected, false);
298
+ });
299
+
300
+ it('should warn on meta schema with implicit values', () => {
301
+ /**
302
+ * Tests handling of malformed meta blocks.
303
+ * In the input: / listAttr="GlobalList" / is missing '$' prefix for attribute.
304
+ */
305
+ const akdText = `
302
306
  <
303
307
  / listAttr="GlobalList" /
304
308
  [
@@ -309,34 +313,35 @@ describe('AK Data Metadata (Meta)', () => {
309
313
  >
310
314
  [ (1) ]
311
315
  `;
312
-
313
- const results = decode(akdText, { debug: false });
314
- const node = results.node;
315
- const warnings = results.warnings;
316
-
317
- // If the input was fixed above, errors should be 0.
318
- // Because we have: listAttr="GlobalList" (implicit), it should be a warning.
319
- expect(warnings.length).toBeGreaterThan(0);
320
- expect(warnings[0].message).toContain("Implicit attribute 'listAttr'");
321
-
322
- // 1. Check List Meta (Outer)
323
- expect(node.isList).toBe(true);
324
- expect(node.schema.attr["listAttr"]).toBe("GlobalList");
325
-
326
- // 2. Check Element Meta (Inner Record)
327
- const elemSchema = node.schema.element!;
328
- expect(elemSchema.kind).toBe(SchemaKind.RECORD);
329
- expect(node.schema.attr["elemAttr"]).toBe("InnerRecord");
330
-
331
- const expected = '<[//*fixed input*/ $listAttr="GlobalList" $elemAttr="InnerRecord"/ /*Missing $ prefix*/ /*comments2*/ id:number]>[(1)]';
332
- assertRoundtrip(akdText, expected, false);
333
- });
334
316
 
335
- it('should handle meta schema field modifiers', () => {
336
- /**
337
- * Tests field modifiers inside a record definition: !required, $key=value.
338
- */
339
- const akdText = `
317
+ const results = decode(akdText, { debug: false });
318
+ const node = results.node;
319
+ const warnings = results.warnings;
320
+
321
+ // If the input was fixed above, errors should be 0.
322
+ // Because we have: listAttr="GlobalList" (implicit), it should be a warning.
323
+ expect(warnings.length).toBeGreaterThan(0);
324
+ expect(warnings[0].message).toContain("Implicit attribute 'listAttr'");
325
+
326
+ // 1. Check List Meta (Outer)
327
+ expect(node.isList).toBe(true);
328
+ expect(node.schema.attr['listAttr']).toBe('GlobalList');
329
+
330
+ // 2. Check Element Meta (Inner Record)
331
+ const elemSchema = node.schema.element!;
332
+ expect(elemSchema.kind).toBe(SchemaKind.RECORD);
333
+ expect(node.schema.attr['elemAttr']).toBe('InnerRecord');
334
+
335
+ const expected =
336
+ '<[//*fixed input*/ $listAttr="GlobalList" $elemAttr="InnerRecord"/ /*Missing $ prefix*/ /*comments2*/ id:number]>[(1)]';
337
+ assertRoundtrip(akdText, expected, false);
338
+ });
339
+
340
+ it('should handle meta schema field modifiers', () => {
341
+ /**
342
+ * Tests field modifiers inside a record definition: !required, $key=value.
343
+ */
344
+ const akdText = `
340
345
  <
341
346
  /* comm0 */
342
347
  / $id=0 /*comm2 /* comm2.5*/ */ /
@@ -351,74 +356,74 @@ describe('AK Data Metadata (Meta)', () => {
351
356
  >
352
357
  ( /* comment0 */ / $id=3 /*comment2*/ / /*comment3*/ 1, "Alice" $id=65 #alice /*comment4*/ )
353
358
  `;
354
-
355
- const results = decode(akdText, { debug: false });
356
- const node = results.node;
357
- expect(results.errors).toHaveLength(0);
358
-
359
- expect(node.isRecord).toBe(true);
360
-
361
- // Retrieve field definitions from schema
362
- const fields = node.schema.fields;
363
-
364
- // Field 'id'
365
- const fId = fields.find(f => f.name === "id")!;
366
- expect(fId).toBeDefined();
367
- expect(fId.required).toBe(true);
368
- expect(fId.attr["key"]).toBe(101);
369
-
370
- // Field 'name'
371
- const fName = fields.find(f => f.name === "name")!;
372
- expect(fName).toBeDefined();
373
- expect(fName.required).toBe(false); // Default
374
- expect(fName.attr["desc"]).toBe("User Name");
375
-
376
- // Check Instance Data Meta (the node itself, not the schema)
377
- // The record instance has / $id=3 /
378
- expect(node.attr["id"]).toBe(3);
379
-
380
- const expected = '<//*comm2 /* comm2.5*/*/ $id=0/ /*comm0*/ /*comm3*/ /*Modifiers block before field name*/ !required $key=101 id:number,$desc="User Name" name:string>(//*comment2*/ $id=3/ /*comment0*/ /*comment3*/ 1,/*comment4*/ $id=65 #alice "Alice")';
381
- assertRoundtrip(akdText, expected, false);
382
- });
383
-
384
-
385
- // ==============================================================================
386
- // 2. DATA BLOCK META (Metadata inside data blocks [ ... ])
387
- // ==============================================================================
388
-
389
- it('should handle meta data block list primitive', () => {
390
- /**
391
- * Tests metadata inside a data block for a simple list.
392
- * Syntax: [ / @size=3 / 1, 2, 3 ]
393
- */
394
- const akdText = '[ / $size=3 $author="me" / 1, 2, 3 ]';
395
-
396
- const results = decode(akdText, { debug: false });
397
- const node = results.node;
398
- expect(results.errors).toHaveLength(0);
399
-
400
- expect(node.isList).toBe(true);
401
- // Meta should go to this specific node's attributes
402
- expect(node.attr["size"]).toBe(3);
403
- expect(node.attr["author"]).toBe("me");
404
-
405
- // Check content
406
- expect(node.elements).toHaveLength(3);
407
- expect(node.elements[0].value).toBe(1);
408
-
409
- const expected = '<[number]>[/$size=3 $author="me"/ 1,2,3]';
410
- assertRoundtrip(akdText, expected, false);
411
- });
412
-
413
- // ==============================================================================
414
- // 3. NESTED META (Lists within lists)
415
- // ==============================================================================
416
359
 
417
- it('should handle meta nested lists', () => {
418
- /**
419
- * Tests metadata assignment in nested lists.
420
- */
421
- const akdText = `
360
+ const results = decode(akdText, { debug: false });
361
+ const node = results.node;
362
+ expect(results.errors).toHaveLength(0);
363
+
364
+ expect(node.isRecord).toBe(true);
365
+
366
+ // Retrieve field definitions from schema
367
+ const fields = node.schema.fields;
368
+
369
+ // Field 'id'
370
+ const fId = fields.find((f) => f.name === 'id')!;
371
+ expect(fId).toBeDefined();
372
+ expect(fId.required).toBe(true);
373
+ expect(fId.attr['key']).toBe(101);
374
+
375
+ // Field 'name'
376
+ const fName = fields.find((f) => f.name === 'name')!;
377
+ expect(fName).toBeDefined();
378
+ expect(fName.required).toBe(false); // Default
379
+ expect(fName.attr['desc']).toBe('User Name');
380
+
381
+ // Check Instance Data Meta (the node itself, not the schema)
382
+ // The record instance has / $id=3 /
383
+ expect(node.attr['id']).toBe(3);
384
+
385
+ const expected =
386
+ '<//*comm2 /* comm2.5*/*/ $id=0/ /*comm0*/ /*comm3*/ /*Modifiers block before field name*/ !required $key=101 id:number,$desc="User Name" name:string>(//*comment2*/ $id=3/ /*comment0*/ /*comment3*/ 1,/*comment4*/ $id=65 #alice "Alice")';
387
+ assertRoundtrip(akdText, expected, false);
388
+ });
389
+
390
+ // ==============================================================================
391
+ // 2. DATA BLOCK META (Metadata inside data blocks [ ... ])
392
+ // ==============================================================================
393
+
394
+ it('should handle meta data block list primitive', () => {
395
+ /**
396
+ * Tests metadata inside a data block for a simple list.
397
+ * Syntax: [ / @size=3 / 1, 2, 3 ]
398
+ */
399
+ const akdText = '[ / $size=3 $author="me" / 1, 2, 3 ]';
400
+
401
+ const results = decode(akdText, { debug: false });
402
+ const node = results.node;
403
+ expect(results.errors).toHaveLength(0);
404
+
405
+ expect(node.isList).toBe(true);
406
+ // Meta should go to this specific node's attributes
407
+ expect(node.attr['size']).toBe(3);
408
+ expect(node.attr['author']).toBe('me');
409
+
410
+ // Check content
411
+ expect(node.elements).toHaveLength(3);
412
+ expect(node.elements[0].value).toBe(1);
413
+
414
+ const expected = '<[number]>[/$size=3 $author="me"/ 1,2,3]';
415
+ assertRoundtrip(akdText, expected, false);
416
+ });
417
+
418
+ // ==============================================================================
419
+ // 3. NESTED META (Lists within lists)
420
+ // ==============================================================================
421
+
422
+ it('should handle meta nested lists', () => {
423
+ /**
424
+ * Tests metadata assignment in nested lists.
425
+ */
426
+ const akdText = `
422
427
  [
423
428
  / $level=0 /
424
429
  [
@@ -431,76 +436,75 @@ describe('AK Data Metadata (Meta)', () => {
431
436
  ]
432
437
  ]
433
438
  `;
434
-
435
- const results = decode(akdText, { debug: false });
436
- const node = results.node;
437
- expect(results.errors).toHaveLength(0);
438
-
439
- // Root Node
440
- expect(node.isList).toBe(true);
441
- expect(node.attr["level"]).toBe(0);
442
-
443
- // Inner Node 1
444
- const inner1 = node.elements[0];
445
- expect(inner1.isList).toBe(true);
446
- expect(inner1.attr["level"]).toBe(1);
447
-
448
- // Inner Node 2
449
- const inner2 = node.elements[1];
450
- expect(inner2.isList).toBe(true);
451
- expect(inner2.attr["level"]).toBe(2);
452
-
453
- const expected = '<[[number]]>[/$level=0/ [/$level=1/ 1,2],[/$level=2/ 3,4]]';
454
- assertRoundtrip(akdText, expected, false);
455
- });
456
-
457
- // ==============================================================================
458
- // 4. EDGE CASES & OVERRIDES
459
- // ==============================================================================
460
-
461
- it('should handle meta mixed with type override', () => {
462
- /**
463
- * Tests a scenario where we have metadata for the list AND a type override for an element.
464
- */
465
- const akdText = '[ / $info="mixed" / 1, 2, <string> "3" ]';
466
- const expected = '<[number]>[/$info="mixed"/ 1,2,<string> "3"]';
467
-
468
- const result = decode(akdText, { debug: false });
469
- const node = result.node;
470
- expect(result.errors).toHaveLength(0);
471
-
472
- // List Meta
473
- expect(node.attr["info"]).toBe("mixed");
474
-
475
- // List Type Inference (Should be Number based on first element '1')
476
- expect(node.schema.element?.typeName).toBe("number");
477
-
478
- // Element Override
479
- const elLast = node.elements[2];
480
- expect(elLast.schema.typeName).toBe("string");
481
- expect(elLast.value).toBe("3");
482
-
483
- assertRoundtrip(node, expected, false);
484
- });
485
-
486
- it('should handle meta and explicit type in data', () => {
487
- /**
488
- * Tests a scenario where an explicit type is provided inside the / ... / block.
489
- * The parser must understand that type is the list type, and @tag is metadata.
490
- */
491
- const akdText = '[ / $tag=1 / 1, 2 ]';
492
-
493
- const result = decode(akdText, { debug: false });
494
- const node = result.node;
495
- expect(result.errors).toHaveLength(0);
496
-
497
- expect(node.isList).toBe(true);
498
- // Inferred type
499
- expect(node.schema.element?.typeName).toBe("number");
500
- expect(Object.keys(node.attr)).toHaveLength(1);
501
-
502
- const expected = "<[number]>[/$tag=1/ 1,2]";
503
- assertRoundtrip(node, expected, false);
504
- });
505
439
 
506
- });
440
+ const results = decode(akdText, { debug: false });
441
+ const node = results.node;
442
+ expect(results.errors).toHaveLength(0);
443
+
444
+ // Root Node
445
+ expect(node.isList).toBe(true);
446
+ expect(node.attr['level']).toBe(0);
447
+
448
+ // Inner Node 1
449
+ const inner1 = node.elements[0];
450
+ expect(inner1.isList).toBe(true);
451
+ expect(inner1.attr['level']).toBe(1);
452
+
453
+ // Inner Node 2
454
+ const inner2 = node.elements[1];
455
+ expect(inner2.isList).toBe(true);
456
+ expect(inner2.attr['level']).toBe(2);
457
+
458
+ const expected = '<[[number]]>[/$level=0/ [/$level=1/ 1,2],[/$level=2/ 3,4]]';
459
+ assertRoundtrip(akdText, expected, false);
460
+ });
461
+
462
+ // ==============================================================================
463
+ // 4. EDGE CASES & OVERRIDES
464
+ // ==============================================================================
465
+
466
+ it('should handle meta mixed with type override', () => {
467
+ /**
468
+ * Tests a scenario where we have metadata for the list AND a type override for an element.
469
+ */
470
+ const akdText = '[ / $info="mixed" / 1, 2, <string> "3" ]';
471
+ const expected = '<[number]>[/$info="mixed"/ 1,2,<string> "3"]';
472
+
473
+ const result = decode(akdText, { debug: false });
474
+ const node = result.node;
475
+ expect(result.errors).toHaveLength(0);
476
+
477
+ // List Meta
478
+ expect(node.attr['info']).toBe('mixed');
479
+
480
+ // List Type Inference (Should be Number based on first element '1')
481
+ expect(node.schema.element?.typeName).toBe('number');
482
+
483
+ // Element Override
484
+ const elLast = node.elements[2];
485
+ expect(elLast.schema.typeName).toBe('string');
486
+ expect(elLast.value).toBe('3');
487
+
488
+ assertRoundtrip(node, expected, false);
489
+ });
490
+
491
+ it('should handle meta and explicit type in data', () => {
492
+ /**
493
+ * Tests a scenario where an explicit type is provided inside the / ... / block.
494
+ * The parser must understand that type is the list type, and @tag is metadata.
495
+ */
496
+ const akdText = '[ / $tag=1 / 1, 2 ]';
497
+
498
+ const result = decode(akdText, { debug: false });
499
+ const node = result.node;
500
+ expect(result.errors).toHaveLength(0);
501
+
502
+ expect(node.isList).toBe(true);
503
+ // Inferred type
504
+ expect(node.schema.element?.typeName).toBe('number');
505
+ expect(Object.keys(node.attr)).toHaveLength(1);
506
+
507
+ const expected = '<[number]>[/$tag=1/ 1,2]';
508
+ assertRoundtrip(node, expected, false);
509
+ });
510
+ });