@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.
- package/.prettierrc +8 -0
- package/README.md +166 -112
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/core/Decoder.d.ts.map +1 -1
- package/dist/core/Decoder.js +123 -97
- package/dist/core/Decoder.js.map +1 -1
- package/dist/core/Encoder.d.ts +1 -2
- package/dist/core/Encoder.d.ts.map +1 -1
- package/dist/core/Encoder.js +74 -76
- package/dist/core/Encoder.js.map +1 -1
- package/dist/core/Parser.d.ts +1 -1
- package/dist/core/Parser.d.ts.map +1 -1
- package/dist/core/Parser.js +11 -11
- package/dist/core/Parser.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -8
- package/dist/index.js.map +1 -1
- package/dist/models/Meta.d.ts +3 -2
- package/dist/models/Meta.d.ts.map +1 -1
- package/dist/models/Meta.js +3 -3
- package/dist/models/Meta.js.map +1 -1
- package/dist/models/Node.d.ts +4 -3
- package/dist/models/Node.d.ts.map +1 -1
- package/dist/models/Node.js +29 -23
- package/dist/models/Node.js.map +1 -1
- package/dist/models/Schema.d.ts.map +1 -1
- package/dist/models/Schema.js +27 -21
- package/dist/models/Schema.js.map +1 -1
- package/eslint.config.mjs +42 -0
- package/package.json +11 -1
- package/scripts/verify-build.js +95 -92
- package/src/config.ts +75 -75
- package/src/core/Decoder.ts +984 -922
- package/src/core/Encoder.ts +364 -371
- package/src/core/Parser.ts +112 -112
- package/src/index.ts +18 -20
- package/src/models/Meta.ts +107 -107
- package/src/models/Node.ts +190 -185
- package/src/models/Schema.ts +198 -193
- package/tests/00.meta.test.ts +19 -25
- package/tests/00.node.test.ts +40 -48
- package/tests/00.primitive.test.ts +121 -95
- package/tests/00.schema.test.ts +28 -35
- package/tests/01.schema.test.ts +42 -52
- package/tests/02.data.test.ts +69 -75
- package/tests/03.errors.test.ts +53 -55
- package/tests/04.list.test.ts +192 -193
- package/tests/05.record.test.ts +54 -56
- package/tests/06.meta.test.ts +393 -389
- package/tests/utils.ts +47 -44
- package/tsconfig.json +27 -29
- package/vitest.config.ts +1 -1
package/tests/06.meta.test.ts
CHANGED
|
@@ -1,31 +1,30 @@
|
|
|
1
|
-
import { describe,
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
274
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
});
|
|
292
|
+
const results = decode(akdText, { debug: false });
|
|
293
|
+
expect(results.errors).toHaveLength(0);
|
|
295
294
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
+
});
|