@arkadia/data 0.1.9 → 0.1.11
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/README.md +54 -10
- package/dist/core/Decoder.d.ts +6 -1
- package/dist/core/Decoder.d.ts.map +1 -1
- package/dist/core/Decoder.js +51 -31
- package/dist/core/Decoder.js.map +1 -1
- package/dist/core/Encoder.d.ts +5 -2
- package/dist/core/Encoder.d.ts.map +1 -1
- package/dist/core/Encoder.js +114 -53
- package/dist/core/Encoder.js.map +1 -1
- package/dist/models/Meta.d.ts +2 -2
- package/dist/models/Meta.js +3 -3
- package/dist/models/Schema.js +1 -1
- package/package.json +1 -1
- package/src/core/Decoder.ts +58 -30
- package/src/core/Encoder.ts +120 -60
- package/src/models/Meta.ts +3 -3
- package/src/models/Schema.ts +1 -1
- package/tests/00.meta.test.ts +2 -2
- package/tests/00.primitive.test.ts +38 -0
- package/tests/00.schema.test.ts +2 -2
- package/tests/04.list.test.ts +25 -2
- package/tests/06.meta.test.ts +57 -251
- package/tests/07.prompt-output.test.ts +117 -0
package/tests/06.meta.test.ts
CHANGED
|
@@ -8,7 +8,7 @@ describe('AK Data Metadata (Meta)', () => {
|
|
|
8
8
|
// ==================================================================================
|
|
9
9
|
|
|
10
10
|
it('should handle comments', () => {
|
|
11
|
-
/** Validates that comments
|
|
11
|
+
/** Validates that comments / * ... * / are handled correctly. */
|
|
12
12
|
const text = '@User<id:int /*primary key*/, name:string> @User(5, "Bob")';
|
|
13
13
|
// Note: int normalizes to number in TS
|
|
14
14
|
const expected = '@User</*primary key*/ id:number,name:string>(5,"Bob")';
|
|
@@ -16,8 +16,6 @@ describe('AK Data Metadata (Meta)', () => {
|
|
|
16
16
|
const res = decode(text, { debug: false });
|
|
17
17
|
expect(res.errors).toHaveLength(0);
|
|
18
18
|
|
|
19
|
-
// If the parser attaches schema comments to fields correctly, good.
|
|
20
|
-
// Here we just ensure data parsing works despite comments.
|
|
21
19
|
expect(res.node.fields['id'].value).toBe(5);
|
|
22
20
|
|
|
23
21
|
assertRoundtrip(text, expected, false);
|
|
@@ -28,14 +26,14 @@ describe('AK Data Metadata (Meta)', () => {
|
|
|
28
26
|
$a0=5
|
|
29
27
|
<
|
|
30
28
|
/* c1 */
|
|
31
|
-
|
|
29
|
+
// $a1 /* c0 *///
|
|
32
30
|
/* c2 */ $a2=2 /* c3 */ $a3=3 a:number
|
|
33
31
|
>
|
|
34
32
|
($a6 /*a*/ 3)
|
|
35
33
|
`;
|
|
36
34
|
|
|
37
35
|
const expected =
|
|
38
|
-
'
|
|
36
|
+
'<///*c0*/ $a0=5 $a1// /*c1*/ /*c2*/ /*c3*/ $a2=2 $a3=3 a:number>(/*a*/ $a6 3)';
|
|
39
37
|
assertRoundtrip(akdText, expected, false);
|
|
40
38
|
});
|
|
41
39
|
|
|
@@ -44,21 +42,20 @@ describe('AK Data Metadata (Meta)', () => {
|
|
|
44
42
|
$attr=5
|
|
45
43
|
<
|
|
46
44
|
/* comm2 */
|
|
47
|
-
|
|
45
|
+
// $schema1 //
|
|
48
46
|
/* comm1 */
|
|
49
47
|
[a:int]
|
|
50
48
|
>
|
|
51
49
|
$attr=3
|
|
52
50
|
[
|
|
53
|
-
|
|
51
|
+
// /*meta for list*/ $attr=4 //
|
|
54
52
|
/*item1*/ $attr5 (3 $attr6),
|
|
55
53
|
/*item2*/ {a:5},
|
|
56
54
|
]
|
|
57
55
|
`;
|
|
58
56
|
|
|
59
|
-
// Note: int -> number
|
|
60
57
|
const expected =
|
|
61
|
-
'<[
|
|
58
|
+
'<[///*comm2*/ /*comm1*/ $attr=5 $schema1// a:number]>[///*meta for list*/ $attr=4// (///*item1*/ $attr5// $attr6 3),(///*item2*/// 5)]';
|
|
62
59
|
assertRoundtrip(akdText, expected, false);
|
|
63
60
|
});
|
|
64
61
|
|
|
@@ -67,17 +64,11 @@ describe('AK Data Metadata (Meta)', () => {
|
|
|
67
64
|
// ==============================================================================
|
|
68
65
|
|
|
69
66
|
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
67
|
const akdText = `
|
|
77
68
|
/* 0 */
|
|
78
69
|
<
|
|
79
70
|
/* commentm0 */ /* com1 /*com1.2*/ */
|
|
80
|
-
|
|
71
|
+
// $listAttr="GlobalList" $b=4 #tag //
|
|
81
72
|
/* comment4 */
|
|
82
73
|
id:number
|
|
83
74
|
>
|
|
@@ -93,152 +84,78 @@ describe('AK Data Metadata (Meta)', () => {
|
|
|
93
84
|
|
|
94
85
|
const result = decode(akdText, { debug: false });
|
|
95
86
|
const node = result.node;
|
|
96
|
-
const errors = result.errors;
|
|
97
87
|
|
|
98
|
-
|
|
99
|
-
expect(errors).toHaveLength(0);
|
|
88
|
+
expect(result.errors).toHaveLength(0);
|
|
100
89
|
|
|
101
|
-
// 2. Check Schema Basics
|
|
102
90
|
const schema = node.schema;
|
|
103
91
|
expect(schema).not.toBeNull();
|
|
104
|
-
expect(schema
|
|
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);
|
|
92
|
+
expect(schema!.kind).toBe(SchemaKind.LIST);
|
|
110
93
|
|
|
111
|
-
|
|
112
|
-
expect(schema
|
|
113
|
-
expect(schema
|
|
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);
|
|
94
|
+
expect(schema!.attr['listAttr']).toBe('GlobalList');
|
|
95
|
+
expect(schema!.attr['b']).toBe(4);
|
|
96
|
+
expect(schema!.tags).toContain('tag');
|
|
119
97
|
|
|
120
98
|
const expected =
|
|
121
|
-
'<[
|
|
99
|
+
'<[///*0*/ $listAttr="GlobalList" $b=4 #tag// number]>[///*a*/ /*b*/ $val=3 #tag1// 1,2,3]';
|
|
122
100
|
assertRoundtrip(akdText, expected, false);
|
|
123
101
|
});
|
|
124
102
|
|
|
125
103
|
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
104
|
const schema = new Schema(SchemaKind.RECORD);
|
|
133
105
|
schema.comments = ['comment1', 'comment2'];
|
|
134
106
|
schema.attr = { key: 'value', count: 10 };
|
|
135
107
|
schema.tags = ['myTag'];
|
|
136
108
|
|
|
137
|
-
// Create a node using this schema
|
|
138
109
|
const node = new Node(schema, { value: null });
|
|
139
|
-
const expected = '
|
|
110
|
+
const expected = '<///*comment1*/ /*comment2*/ $key="value" $count=10 #myTag// any>(null)';
|
|
140
111
|
|
|
141
112
|
assertRoundtrip(node, expected, false);
|
|
142
113
|
});
|
|
143
114
|
|
|
144
115
|
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
116
|
const originalSchema = new Schema(SchemaKind.RECORD);
|
|
152
117
|
originalSchema.comments = ['comment1', 'comment2'];
|
|
153
118
|
originalSchema.attr = { key: 'value', count: 10, isActive: true };
|
|
154
119
|
originalSchema.tags = ['myTag', 'urgent'];
|
|
155
120
|
|
|
156
|
-
// Create a node using this schema
|
|
157
121
|
const originalNode = new Node(originalSchema, { value: null });
|
|
158
122
|
|
|
159
|
-
// 2. Encode to String
|
|
160
|
-
// Important: We must enable include_comments to verify them after decoding
|
|
161
123
|
const encodedText = encode(originalNode, {
|
|
162
124
|
includeComments: true,
|
|
163
|
-
compact: true,
|
|
125
|
+
compact: true,
|
|
164
126
|
colorize: false,
|
|
165
127
|
});
|
|
166
128
|
|
|
167
|
-
// 3. Decode back to Node
|
|
168
129
|
const result = decode(encodedText, { debug: false });
|
|
169
130
|
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
131
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
expect(
|
|
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');
|
|
132
|
+
expect(result.errors).toHaveLength(0);
|
|
133
|
+
expect(decodedNode.schema!.attr['key']).toBe('value');
|
|
134
|
+
expect(decodedNode.schema!.tags).toContain('urgent');
|
|
195
135
|
|
|
196
|
-
//
|
|
136
|
+
// Test cleaning meta
|
|
197
137
|
const decodedTextClean = encode(decodedNode, {
|
|
198
138
|
compact: true,
|
|
199
139
|
includeMeta: false,
|
|
200
140
|
includeComments: false,
|
|
201
141
|
});
|
|
202
142
|
|
|
203
|
-
// Assertions for No Meta
|
|
204
143
|
expect(decodedTextClean).not.toContain('$key');
|
|
205
|
-
expect(decodedTextClean).not.toContain('
|
|
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,
|
|
216
|
-
});
|
|
217
|
-
|
|
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('/');
|
|
144
|
+
expect(decodedTextClean).not.toContain('//');
|
|
223
145
|
|
|
224
146
|
const expected =
|
|
225
|
-
'
|
|
147
|
+
'<///*comment1*/ /*comment2*/ $key="value" $count=10 $isActive #myTag #urgent// any>(null)';
|
|
226
148
|
assertRoundtrip(originalNode, expected, false);
|
|
227
149
|
});
|
|
228
150
|
|
|
229
151
|
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
152
|
const akdText = `
|
|
236
153
|
<
|
|
237
154
|
/* comm-header-0 */ /* comm-header-1 /* comm-header-1.1*/ */
|
|
238
|
-
|
|
155
|
+
// $listAttr="GlobalList" $b=4 /*com-in*/ //
|
|
239
156
|
/* comm-after-header-0 */
|
|
240
157
|
[
|
|
241
|
-
|
|
158
|
+
// $elemAttr="InnerRecord" #elem0 /* comm-inside-header-0 */ //
|
|
242
159
|
/* comm-inside-field-0 */ #elem1 id: int
|
|
243
160
|
]
|
|
244
161
|
>
|
|
@@ -247,30 +164,13 @@ describe('AK Data Metadata (Meta)', () => {
|
|
|
247
164
|
|
|
248
165
|
const results = decode(akdText, { debug: false });
|
|
249
166
|
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
167
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
expect(
|
|
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({});
|
|
168
|
+
expect(results.errors).toHaveLength(0);
|
|
169
|
+
expect(node.schema!.attr['listAttr']).toBe('GlobalList');
|
|
170
|
+
expect(node.schema!.attr['elemAttr']).toBe('InnerRecord');
|
|
271
171
|
|
|
272
172
|
const expected =
|
|
273
|
-
'<[
|
|
173
|
+
'<[///*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
174
|
assertRoundtrip(akdText, expected, false);
|
|
275
175
|
});
|
|
276
176
|
|
|
@@ -278,12 +178,12 @@ describe('AK Data Metadata (Meta)', () => {
|
|
|
278
178
|
const akdText = `
|
|
279
179
|
<
|
|
280
180
|
/* header-com-0 */
|
|
281
|
-
|
|
181
|
+
// #tag_header //
|
|
282
182
|
/* comm-data-v1 */ #tag1 v1: number /* comm-data-v2 */ #tag2,
|
|
283
183
|
/* comm-data-v3 */ #tag3 v2: number /* comm-data-v3 */ #tag4
|
|
284
184
|
>
|
|
285
185
|
[
|
|
286
|
-
|
|
186
|
+
// #tag_list //
|
|
287
187
|
/* comm-data-v1 */ #tag1 1 /* comm-data-v2 */ #tag2
|
|
288
188
|
/* comm-data-v3 */ #tag3 2 /* comm-data-v3 */ #tag4
|
|
289
189
|
]
|
|
@@ -293,21 +193,17 @@ describe('AK Data Metadata (Meta)', () => {
|
|
|
293
193
|
expect(results.errors).toHaveLength(0);
|
|
294
194
|
|
|
295
195
|
const expected =
|
|
296
|
-
'<[
|
|
196
|
+
'<[//#tag_header// number]>[//#tag_list// /*comm-data-v1*/ #tag1 1,/*comm-data-v2*/ /*comm-data-v3*/ /*comm-data-v3*/ #tag2 #tag3 #tag4 2]';
|
|
297
197
|
assertRoundtrip(akdText, expected, false);
|
|
298
198
|
});
|
|
299
199
|
|
|
300
200
|
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
201
|
const akdText = `
|
|
306
202
|
<
|
|
307
|
-
|
|
203
|
+
// listAttr="GlobalList" //
|
|
308
204
|
[
|
|
309
205
|
/* Missing $ prefix */
|
|
310
|
-
|
|
206
|
+
// $elemAttr="InnerRecord" /* fixed input */ //
|
|
311
207
|
/* comments2 */ id: int
|
|
312
208
|
]
|
|
313
209
|
>
|
|
@@ -315,123 +211,69 @@ describe('AK Data Metadata (Meta)', () => {
|
|
|
315
211
|
`;
|
|
316
212
|
|
|
317
213
|
const results = decode(akdText, { debug: false });
|
|
318
|
-
|
|
319
|
-
|
|
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');
|
|
214
|
+
expect(results.warnings.length).toBeGreaterThan(0);
|
|
215
|
+
expect(results.warnings[0].message).toContain("Implicit attribute 'listAttr'");
|
|
334
216
|
|
|
335
217
|
const expected =
|
|
336
|
-
'<[
|
|
218
|
+
'<[///*fixed input*/ $listAttr="GlobalList" $elemAttr="InnerRecord"// /*Missing $ prefix*/ /*comments2*/ id:number]>[(1)]';
|
|
337
219
|
assertRoundtrip(akdText, expected, false);
|
|
338
220
|
});
|
|
339
221
|
|
|
340
222
|
it('should handle meta schema field modifiers', () => {
|
|
341
|
-
/**
|
|
342
|
-
* Tests field modifiers inside a record definition: !required, $key=value.
|
|
343
|
-
*/
|
|
344
223
|
const akdText = `
|
|
345
224
|
<
|
|
346
225
|
/* comm0 */
|
|
347
|
-
|
|
226
|
+
// $id=0 /*comm2 /* comm2.5*/ */ //
|
|
348
227
|
|
|
349
228
|
/* comm3 */
|
|
350
229
|
|
|
351
230
|
/* Modifiers block before field name */
|
|
352
|
-
|
|
231
|
+
$required $key=101 id:int,
|
|
353
232
|
|
|
354
233
|
$desc="User Name"
|
|
355
234
|
name: string
|
|
356
235
|
>
|
|
357
|
-
( /* comment0 */
|
|
236
|
+
( /* comment0 */ // $id=3 /*comment2*/ // /*comment3*/ 1, "Alice" $id=65 #alice /*comment4*/ )
|
|
358
237
|
`;
|
|
359
238
|
|
|
360
239
|
const results = decode(akdText, { debug: false });
|
|
361
240
|
const node = results.node;
|
|
362
241
|
expect(results.errors).toHaveLength(0);
|
|
363
242
|
|
|
364
|
-
|
|
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();
|
|
243
|
+
const fId = node.schema!.fields.find((f) => f.name === 'id')!;
|
|
372
244
|
expect(fId.required).toBe(true);
|
|
373
245
|
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
246
|
expect(node.attr['id']).toBe(3);
|
|
384
247
|
|
|
385
248
|
const expected =
|
|
386
|
-
'
|
|
249
|
+
'<///*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
250
|
assertRoundtrip(akdText, expected, false);
|
|
388
251
|
});
|
|
389
252
|
|
|
390
|
-
// ==============================================================================
|
|
391
|
-
// 2. DATA BLOCK META (Metadata inside data blocks [ ... ])
|
|
392
|
-
// ==============================================================================
|
|
393
|
-
|
|
394
253
|
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 ]';
|
|
254
|
+
const akdText = '[ // $size=3 $author="me" // 1, 2, 3 ]';
|
|
400
255
|
|
|
401
256
|
const results = decode(akdText, { debug: false });
|
|
402
257
|
const node = results.node;
|
|
403
258
|
expect(results.errors).toHaveLength(0);
|
|
404
259
|
|
|
405
|
-
expect(node.isList).toBe(true);
|
|
406
|
-
// Meta should go to this specific node's attributes
|
|
407
260
|
expect(node.attr['size']).toBe(3);
|
|
408
261
|
expect(node.attr['author']).toBe('me');
|
|
409
262
|
|
|
410
|
-
//
|
|
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]';
|
|
263
|
+
const expected = '<[number]>[//$size=3 $author="me"// 1,2,3]';
|
|
415
264
|
assertRoundtrip(akdText, expected, false);
|
|
416
265
|
});
|
|
417
266
|
|
|
418
|
-
// ==============================================================================
|
|
419
|
-
// 3. NESTED META (Lists within lists)
|
|
420
|
-
// ==============================================================================
|
|
421
|
-
|
|
422
267
|
it('should handle meta nested lists', () => {
|
|
423
|
-
/**
|
|
424
|
-
* Tests metadata assignment in nested lists.
|
|
425
|
-
*/
|
|
426
268
|
const akdText = `
|
|
427
269
|
[
|
|
428
|
-
|
|
270
|
+
// $level=0 //
|
|
429
271
|
[
|
|
430
|
-
|
|
272
|
+
// $level=1 //
|
|
431
273
|
1, 2
|
|
432
274
|
],
|
|
433
275
|
[
|
|
434
|
-
|
|
276
|
+
// $level=2 //
|
|
435
277
|
3, 4
|
|
436
278
|
]
|
|
437
279
|
]
|
|
@@ -441,70 +283,34 @@ describe('AK Data Metadata (Meta)', () => {
|
|
|
441
283
|
const node = results.node;
|
|
442
284
|
expect(results.errors).toHaveLength(0);
|
|
443
285
|
|
|
444
|
-
// Root Node
|
|
445
|
-
expect(node.isList).toBe(true);
|
|
446
286
|
expect(node.attr['level']).toBe(0);
|
|
287
|
+
expect(node.elements[0].attr['level']).toBe(1);
|
|
288
|
+
expect(node.elements[1].attr['level']).toBe(2);
|
|
447
289
|
|
|
448
|
-
//
|
|
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]]';
|
|
290
|
+
const expected = '<[[number]]>[//$level=0// [//$level=1// 1,2],[//$level=2// 3,4]]';
|
|
459
291
|
assertRoundtrip(akdText, expected, false);
|
|
460
292
|
});
|
|
461
293
|
|
|
462
|
-
// ==============================================================================
|
|
463
|
-
// 4. EDGE CASES & OVERRIDES
|
|
464
|
-
// ==============================================================================
|
|
465
|
-
|
|
466
294
|
it('should handle meta mixed with type override', () => {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
*/
|
|
470
|
-
const akdText = '[ / $info="mixed" / 1, 2, <string> "3" ]';
|
|
471
|
-
const expected = '<[number]>[/$info="mixed"/ 1,2,<string> "3"]';
|
|
295
|
+
const akdText = '[ // $info="mixed" // 1, 2, <string> "3" ]';
|
|
296
|
+
const expected = '<[number]>[//$info="mixed"// 1,2,<string> "3"]';
|
|
472
297
|
|
|
473
298
|
const result = decode(akdText, { debug: false });
|
|
474
|
-
const node = result.node;
|
|
475
299
|
expect(result.errors).toHaveLength(0);
|
|
300
|
+
expect(result.node.attr['info']).toBe('mixed');
|
|
301
|
+
expect(result.node.schema!.element?.typeName).toBe('number');
|
|
476
302
|
|
|
477
|
-
|
|
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);
|
|
303
|
+
assertRoundtrip(result.node, expected, false);
|
|
489
304
|
});
|
|
490
305
|
|
|
491
306
|
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 ]';
|
|
307
|
+
const akdText = '[ // $tag=1 // 1, 2 ]';
|
|
497
308
|
|
|
498
309
|
const result = decode(akdText, { debug: false });
|
|
499
|
-
const node = result.node;
|
|
500
310
|
expect(result.errors).toHaveLength(0);
|
|
311
|
+
expect(result.node.attr['tag']).toBe(1);
|
|
501
312
|
|
|
502
|
-
|
|
503
|
-
|
|
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);
|
|
313
|
+
const expected = '<[number]>[//$tag=1// 1,2]';
|
|
314
|
+
assertRoundtrip(result.node, expected, false);
|
|
509
315
|
});
|
|
510
316
|
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { decode, encode } from '../src/index';
|
|
3
|
+
|
|
4
|
+
// ==============================================================================
|
|
5
|
+
// 1. PROMPT OUTPUT TESTS (LLM-Friendly Blueprint Mode)
|
|
6
|
+
// ==============================================================================
|
|
7
|
+
|
|
8
|
+
const PROMPT_CONFIG = {
|
|
9
|
+
promptOutput: true,
|
|
10
|
+
compact: false,
|
|
11
|
+
includeSchema: false,
|
|
12
|
+
colorize: false,
|
|
13
|
+
indent: 2,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('AK Data Prompt Output (LLM Blueprints)', () => {
|
|
17
|
+
it('should transform a record into a key-type-comment blueprint', () => {
|
|
18
|
+
/**
|
|
19
|
+
* Validates that promptOutput=true transforms a record into a
|
|
20
|
+
* key-type-comment blueprint using { } braces instead of ( ) parentheses.
|
|
21
|
+
*/
|
|
22
|
+
const akdText = `
|
|
23
|
+
@User <
|
|
24
|
+
id: number /* unique id */,
|
|
25
|
+
name: string /* display name */
|
|
26
|
+
>
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const result = decode(akdText);
|
|
30
|
+
expect(result.errors).toHaveLength(0);
|
|
31
|
+
|
|
32
|
+
const output = encode(result.node, PROMPT_CONFIG).trim();
|
|
33
|
+
|
|
34
|
+
const expected = [
|
|
35
|
+
'{',
|
|
36
|
+
' id: number /* unique id */,',
|
|
37
|
+
' name: string /* display name */',
|
|
38
|
+
'}',
|
|
39
|
+
].join('\n');
|
|
40
|
+
expect(output).toBe(expected);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should show a single example element inside a list with a continuation comment', () => {
|
|
44
|
+
/**
|
|
45
|
+
* Validates that promptOutput=true shows only a single example element
|
|
46
|
+
* inside a list with a continuation comment (...).
|
|
47
|
+
*/
|
|
48
|
+
const akdText = `
|
|
49
|
+
<[ /* id */ id: number, name: string, val: <id: string, num: number> ]>
|
|
50
|
+
[ (1, "n", ("id", 3)), (2), (3) ]
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const result = decode(akdText);
|
|
54
|
+
expect(result.errors).toHaveLength(0);
|
|
55
|
+
|
|
56
|
+
const output = encode(result.node, PROMPT_CONFIG).trim();
|
|
57
|
+
|
|
58
|
+
const expected = [
|
|
59
|
+
'[',
|
|
60
|
+
' {',
|
|
61
|
+
' id: number /* id */,',
|
|
62
|
+
' name: string,',
|
|
63
|
+
' val: {',
|
|
64
|
+
' id: string,',
|
|
65
|
+
' num: number',
|
|
66
|
+
' }',
|
|
67
|
+
' },',
|
|
68
|
+
' ... /* repeat pattern for additional items */',
|
|
69
|
+
']',
|
|
70
|
+
].join('\n');
|
|
71
|
+
|
|
72
|
+
expect(output).toBe(expected);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should expand nested structures into blueprints in prompt mode', () => {
|
|
76
|
+
/**
|
|
77
|
+
* Verifies that nested structures also expand into blueprints in prompt mode.
|
|
78
|
+
*/
|
|
79
|
+
const akdText = `
|
|
80
|
+
<
|
|
81
|
+
name: string,
|
|
82
|
+
meta: < ver: number /* version number */ >
|
|
83
|
+
>
|
|
84
|
+
("App", (1.0))
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
const result = decode(akdText);
|
|
88
|
+
expect(result.errors).toHaveLength(0);
|
|
89
|
+
|
|
90
|
+
const output = encode(result.node, PROMPT_CONFIG).trim();
|
|
91
|
+
|
|
92
|
+
const expected = [
|
|
93
|
+
'{',
|
|
94
|
+
' name: string,',
|
|
95
|
+
' meta: {',
|
|
96
|
+
' ver: number /* version number */',
|
|
97
|
+
' }',
|
|
98
|
+
'}',
|
|
99
|
+
].join('\n');
|
|
100
|
+
expect(output).toBe(expected);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should preserve escaped identifiers (backticks) in prompt mode', () => {
|
|
104
|
+
/**
|
|
105
|
+
* Ensures that escaped identifiers (backticks) are preserved in prompt mode.
|
|
106
|
+
*/
|
|
107
|
+
const akdText = '< `User ID`: number /* system id */ > (123)';
|
|
108
|
+
|
|
109
|
+
const result = decode(akdText);
|
|
110
|
+
expect(result.errors).toHaveLength(0);
|
|
111
|
+
|
|
112
|
+
const output = encode(result.node, PROMPT_CONFIG).trim();
|
|
113
|
+
|
|
114
|
+
const expected = ['{', ' `User ID`: number /* system id */', '}'].join('\n');
|
|
115
|
+
expect(output).toBe(expected);
|
|
116
|
+
});
|
|
117
|
+
});
|