@arkadia/ai-data-format 1.0.0

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/dist/config.d.ts +75 -0
  2. package/dist/config.d.ts.map +1 -0
  3. package/dist/config.js +28 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/core/Decoder.d.ts +88 -0
  6. package/dist/core/Decoder.d.ts.map +1 -0
  7. package/dist/core/Decoder.js +968 -0
  8. package/dist/core/Decoder.js.map +1 -0
  9. package/dist/core/Encoder.d.ts +26 -0
  10. package/dist/core/Encoder.d.ts.map +1 -0
  11. package/dist/core/Encoder.js +368 -0
  12. package/dist/core/Encoder.js.map +1 -0
  13. package/dist/core/Parser.d.ts +6 -0
  14. package/dist/core/Parser.d.ts.map +1 -0
  15. package/dist/core/Parser.js +132 -0
  16. package/dist/core/Parser.js.map +1 -0
  17. package/dist/index.d.ts +22 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +46 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/models/Meta.d.ts +48 -0
  22. package/dist/models/Meta.d.ts.map +1 -0
  23. package/dist/models/Meta.js +113 -0
  24. package/dist/models/Meta.js.map +1 -0
  25. package/dist/models/Node.d.ts +42 -0
  26. package/dist/models/Node.d.ts.map +1 -0
  27. package/dist/models/Node.js +179 -0
  28. package/dist/models/Node.js.map +1 -0
  29. package/dist/models/Schema.d.ts +55 -0
  30. package/dist/models/Schema.d.ts.map +1 -0
  31. package/dist/models/Schema.js +175 -0
  32. package/dist/models/Schema.js.map +1 -0
  33. package/package.json +32 -0
  34. package/src/config.ts +102 -0
  35. package/src/core/Decoder.ts +1074 -0
  36. package/src/core/Encoder.ts +443 -0
  37. package/src/core/Parser.ts +150 -0
  38. package/src/index.ts +46 -0
  39. package/src/models/Meta.ts +135 -0
  40. package/src/models/Node.ts +212 -0
  41. package/src/models/Schema.ts +222 -0
  42. package/tests/00.meta.test.ts +31 -0
  43. package/tests/00.node.test.ts +54 -0
  44. package/tests/00.primitive.test.ts +108 -0
  45. package/tests/00.schema.test.ts +41 -0
  46. package/tests/01.schema.test.ts +70 -0
  47. package/tests/02.data.test.ts +89 -0
  48. package/tests/03.errors.test.ts +71 -0
  49. package/tests/04.list.test.ts +225 -0
  50. package/tests/05.record.test.ts +82 -0
  51. package/tests/06.meta.test.ts +506 -0
  52. package/tests/utils.ts +69 -0
  53. package/tsconfig.json +46 -0
  54. package/vitest.config.ts +9 -0
@@ -0,0 +1,443 @@
1
+ import { Node } from '../models/Node';
2
+ import { Schema } from '../models/Schema';
3
+ import { Meta } from '../models/Meta';
4
+ import { EncoderConfig } from '../config';
5
+
6
+ class Colors {
7
+ static RESET = "\x1b[0m";
8
+ static STRING = "\x1b[92m"; // green
9
+ static NUMBER = "\x1b[94m"; // blue
10
+ static BOOL = "\x1b[95m"; // magenta
11
+ static NULL = "\x1b[90m"; // gray
12
+ static TYPE = "\x1b[96m"; // cyan
13
+ static KEY = "\x1b[93m"; // yellow
14
+ static SCHEMA = "\x1b[91m"; // red (for @TypeName)
15
+ static TAG = "\x1b[91m";
16
+ static ATTR = "\x1b[93m";
17
+ }
18
+
19
+ export class Encoder {
20
+ private config: EncoderConfig;
21
+
22
+ constructor(config: EncoderConfig) {
23
+ this.config = config;
24
+ }
25
+
26
+ // -------------------------------------------------------------
27
+ // PUBLIC ENTRY
28
+ // -------------------------------------------------------------
29
+
30
+ public encode(node: Node, indentLevel: number = 0, includeSchema: boolean = true): string {
31
+ const baseIndent = this.config.startIndent + indentLevel;
32
+ let schemaPrefix = "";
33
+
34
+ // 2. Prepare Schema Header (e.g. <test: string>)
35
+ const shouldRenderSchema = (
36
+ includeSchema &&
37
+ node.schema !== null &&
38
+ this.config.includeSchema
39
+ );
40
+
41
+ if (shouldRenderSchema) {
42
+ let sTxt = this.encodeSchema(node.schema!, baseIndent).trim();
43
+
44
+ // Jeśli wynik nie jest pusty i nie zaczyna się od < ani @, dodaj nawiasy < >
45
+ if (sTxt && !sTxt.startsWith('<') && !sTxt.startsWith('@')) {
46
+ sTxt = `<${sTxt}>`;
47
+ }
48
+
49
+ if (sTxt) {
50
+ if (this.config.compact) {
51
+ schemaPrefix = sTxt;
52
+ } else {
53
+ // Newline formatting for schema header
54
+ schemaPrefix = sTxt + "\n" + " ".repeat(baseIndent);
55
+ }
56
+ }
57
+ }
58
+
59
+ // 3. Encode Data
60
+ let data = "";
61
+ if (node.isList) {
62
+ data = this.encodeList(node, baseIndent, false);
63
+ } else if (node.isPrimitive) {
64
+ data = this.encodePrimitiveNode(node);
65
+ } else if (node.isRecord) {
66
+ data = this.encodeRecord(node, baseIndent);
67
+ } else {
68
+ data = this.c("null", Colors.NULL);
69
+ }
70
+
71
+ // Final Assembly: Meta -> Schema -> Data
72
+ return `${schemaPrefix}${data}`;
73
+ }
74
+
75
+ public encodeSchema(schema: Schema, indent: number, includeMeta: boolean = true): string {
76
+ if (!schema) return "";
77
+
78
+ const ind = " ".repeat(indent);
79
+ let prefix = "";
80
+
81
+ const pad = this.config.compact ? "" : " ";
82
+
83
+ // Avoid printing internal/default type names
84
+ if (
85
+ schema.typeName &&
86
+ schema.isRecord &&
87
+ !schema.isAny
88
+ ) {
89
+ prefix = this.c(`@${schema.typeName}`, Colors.SCHEMA);
90
+ }
91
+
92
+ // Przygotowanie meta (ale jeszcze nie użycie, bo w liście może się zmienić)
93
+ // W Pythonie: meta = self._meta_wrapped(schema) if include_meta else ""
94
+ // Tutaj obliczamy dynamicznie tam gdzie potrzeba, bo w liście modyfikujemy obiekt schema.
95
+
96
+ // --- PRIMITIVE ---
97
+ if (schema.isPrimitive) {
98
+ const metaPrefix = includeMeta ? this.metaInline(schema) : "";
99
+ // Python: return ind + ((meta_prefix + " ") if meta_prefix else "") + self._c(...)
100
+ const metaStr = metaPrefix ? metaPrefix + " " : "";
101
+ return ind + metaStr + this.c(schema.typeName, Colors.TYPE);
102
+ }
103
+
104
+ // --- LIST ---
105
+ // --- DEBUG ---
106
+ if (schema.isList) {
107
+ if(schema.element) {
108
+ schema.applyMeta(schema.element)
109
+ schema.element.clearMeta()
110
+ }
111
+ const listMeta = includeMeta ? this.metaWrapped(schema) : "";
112
+
113
+ // Special Case: List of Records < [ ... ] >
114
+ if (schema.element && schema.element.isRecord) {
115
+ // We reuse the _record_fields logic but wrap in <[ ... ]>
116
+ const innerFields = this.encodeSchemaFields(schema.element);
117
+
118
+ // FIX: Use 'pad' variable to remove spaces in compact mode
119
+ return ind + prefix + "<" + pad + "[" + listMeta + innerFields + pad + "]" + pad + ">";
120
+ }
121
+
122
+ // Standard List [Type]
123
+ const inner = this.encodeSchema(schema.element!, 0, false).trim();
124
+ return ind + "[" + listMeta + this.c(inner, Colors.TYPE) + "]";
125
+ }
126
+
127
+ // --- RECORD ---
128
+ if (schema.isRecord) {
129
+ // Get Record-level meta (e.g. < / $ver=1 / ... >)
130
+ const recordMeta = includeMeta ? this.metaWrapped(schema) : "";
131
+
132
+ if (!schema.fields || schema.fields.length === 0) {
133
+ // If the record is generic (no fields, no specific type name, no meta),
134
+ // return an empty string to avoid printing "<>" or "<any>" before the "{...}".
135
+ if (!prefix && !recordMeta && schema.isAny) {
136
+ return "";
137
+ }
138
+ return ind + prefix + "<" + pad + recordMeta + "any" + pad + ">";
139
+ }
140
+
141
+ // Encode Fields
142
+ const innerFields = this.encodeSchemaFields(schema);
143
+
144
+ // FIX: Use 'pad' variable to remove spaces in compact mode
145
+ return ind + prefix + "<" + pad + recordMeta + innerFields + pad + ">";
146
+ }
147
+
148
+ // Fallback (ANY)
149
+ const meta = includeMeta ? this.metaWrapped(schema) : "";
150
+ return ind + `<${meta}any>`;
151
+ }
152
+
153
+ // --- Helper to deduplicate field encoding logic ---
154
+ private encodeSchemaFields(schema: Schema): string {
155
+ const parts: string[] = [];
156
+ const pad = this.config.compact ? "" : " ";
157
+
158
+ for (const field of schema.fields) {
159
+ let str = "";
160
+
161
+ const metaPrefix = this.metaInline(field);
162
+ if (metaPrefix) {
163
+ str += metaPrefix + " ";
164
+ }
165
+
166
+ // 3. Field Name
167
+ str += this.c(field.name, Colors.KEY);
168
+
169
+ // 4. Field Type
170
+ const fieldType = this.encodeSchema(field, 0, false).trim();
171
+
172
+ // Logic to decide when to print the type signature
173
+ const isStructure = !field.isPrimitive;
174
+ const isExplicitPrimitive = (this.config.includeType && field.typeName !== "any");
175
+
176
+ if (fieldType && (isStructure || isExplicitPrimitive)) {
177
+ str += `:${pad}${this.c(fieldType, Colors.TYPE)}`;
178
+ }
179
+
180
+ parts.push(str);
181
+ }
182
+
183
+ const sep = `,${pad}`;
184
+ return parts.join(sep);
185
+ }
186
+
187
+
188
+ // -------------------------------------------------------------
189
+ // HELPER: SCHEMA COMPATIBILITY CHECK
190
+ // -------------------------------------------------------------
191
+
192
+ private schemasAreCompatible(nodeSchema: Schema | null, expectedSchema: Schema | null): boolean {
193
+ if (!expectedSchema || expectedSchema.isAny) return true;
194
+ if (!nodeSchema) return true;
195
+
196
+ // Check general kind
197
+ if (nodeSchema.kind !== expectedSchema.kind) return false;
198
+
199
+ // Check specific primitive type name (e.g. int vs string)
200
+ if (nodeSchema.isPrimitive && expectedSchema.isPrimitive) {
201
+ return nodeSchema.typeName === expectedSchema.typeName;
202
+ }
203
+
204
+ return true;
205
+ }
206
+
207
+ private getTypeLabel(schema: Schema): string {
208
+ if (schema.isPrimitive) return schema.typeName || "any";
209
+ if (schema.isList) {
210
+ const inner = schema.element ? this.getTypeLabel(schema.element) : "any";
211
+ return `[${inner}]`;
212
+ }
213
+ if (schema.isRecord && schema.typeName && schema.typeName !== "any") {
214
+ return schema.typeName;
215
+ }
216
+ return "any";
217
+ }
218
+
219
+ private applyTypeTag(valStr: string, nodeSchema: Schema | null, expectedSchema: Schema | null): string {
220
+ if (this.schemasAreCompatible(nodeSchema, expectedSchema)) {
221
+ return valStr;
222
+ }
223
+
224
+ // Mismatch detected -> Wrap with tag
225
+ const label = nodeSchema ? this.getTypeLabel(nodeSchema) : "any";
226
+ const tag = this.c(`<${label}>`, Colors.SCHEMA);
227
+ return `${tag} ${valStr}`;
228
+ }
229
+
230
+
231
+ // -------------------------------------------------------------
232
+ // META AND COMMENTS (Unified Logic)
233
+ // -------------------------------------------------------------
234
+
235
+ private buildMetaString(obj: Meta, wrapped: boolean = false): string {
236
+ const items: string[] = [];
237
+ const pad = this.config.compact ? "" : " ";
238
+
239
+ // 1. Comments
240
+ if (this.config.includeComments && obj.comments) {
241
+ for (const c of obj.comments) {
242
+ const cleaned = c.trim();
243
+ items.push(this.c(`/*${pad}${cleaned}${pad}*/`, Colors.NULL));
244
+ }
245
+ }
246
+
247
+ // 2. Modifiers
248
+ // W Pythonie: getattr(obj, 'required', False)
249
+ if ((obj as any).required) {
250
+ items.push(this.c("!required", Colors.TAG));
251
+ }
252
+
253
+ // 3. Attributes & Tags
254
+ if (this.config.includeMeta) {
255
+ const currentAttr = obj.attr || {};
256
+ for (const [k, v] of Object.entries(currentAttr)) {
257
+ const valStr = this.primitiveValue(v);
258
+ items.push(this.c(`$${k}=`, Colors.ATTR) + valStr);
259
+ }
260
+
261
+ const currentTags = obj.tags || [];
262
+ for (const t of currentTags) {
263
+ items.push(this.c(`#${t}`, Colors.TAG));
264
+ }
265
+ }
266
+
267
+ if (items.length === 0) {
268
+ return "";
269
+ }
270
+
271
+ const content = items.join(" ");
272
+
273
+ if (wrapped) {
274
+ const wrappedContent = this.c(`/${pad}`, Colors.SCHEMA) + content + this.c(`${pad}/`, Colors.SCHEMA);
275
+ return this.config.compact ? wrappedContent + " " : " " + wrappedContent + " ";
276
+ }
277
+
278
+ // Inline: /* c1 */ !req $a=1
279
+ return content;
280
+ }
281
+
282
+ private metaInline(obj: Meta): string {
283
+ return this.buildMetaString(obj, false);
284
+ }
285
+
286
+ private metaWrapped(obj: Meta): string {
287
+ return this.buildMetaString(obj, true);
288
+ }
289
+
290
+ // -------------------------------------------------------------
291
+ // HELPERS
292
+ // -------------------------------------------------------------
293
+ private c(text: string, color: string): string {
294
+ if (!this.config.colorize) return text;
295
+ return `${color}${text}${Colors.RESET}`;
296
+ }
297
+
298
+ private escapeNewlines(text: string): string {
299
+ return text.replace(/\r\n/g, "\\n").replace(/\r/g, "\\n").replace(/\n/g, "\\n");
300
+ }
301
+
302
+ private encodePrimitiveNode(node: Node): string {
303
+ const innerMeta = this.metaInline(node);
304
+ const str = this.primitiveValue(node.value);
305
+ return (innerMeta ? innerMeta + " " : "") + str;
306
+ }
307
+
308
+ private primitiveValue(v: any): string {
309
+ if (typeof v === 'string') return this.encodeString(v);
310
+ if (v === true) return this.c("true", Colors.BOOL);
311
+ if (v === false) return this.c("false", Colors.BOOL);
312
+ if (v === null || v === undefined) return this.c("null", Colors.NULL);
313
+ return this.c(String(v), Colors.NUMBER);
314
+ }
315
+
316
+ private encodeString(v: string): string {
317
+ let content = v;
318
+ if (this.config.escapeNewLines) {
319
+ content = this.escapeNewlines(content);
320
+ }
321
+ content = content.replace(/"/g, '\\"');
322
+ return this.c(`"${content}"`, Colors.STRING);
323
+ }
324
+
325
+ // -------------------------------------------------------------
326
+ // LIST
327
+ // -------------------------------------------------------------
328
+
329
+ private listHeader(node: Node): string {
330
+ let header = "[";
331
+ if (this.config.includeArraySize) {
332
+ const size = node.elements.length;
333
+ header += `${this.c('$size', Colors.KEY)}=${this.c(String(size), Colors.NUMBER)}${this.c(':', Colors.TYPE)}`;
334
+ }
335
+ return header;
336
+ }
337
+
338
+ private joinLines(items: string[], sep: string): string {
339
+ if (this.config.compact) return items.join(sep);
340
+ if (sep === "\n") return items.join(sep);
341
+ return items.join(`${sep} `);
342
+ }
343
+
344
+ private encodeList(node: Node, indent: number, includeSchema: boolean = false): string {
345
+ const ind = " ".repeat(indent);
346
+ const childIndent = indent + this.config.indent;
347
+
348
+ const innerMeta = this.metaWrapped(node);
349
+
350
+ // 1. Generate Header Schema (if requested)
351
+ let schemaHeader = "";
352
+ if (includeSchema && node.schema && node.schema.element) {
353
+ schemaHeader = this.encodeSchema(node.schema.element, 0).trim();
354
+ }
355
+ if (schemaHeader) {
356
+ schemaHeader = schemaHeader + " ";
357
+ }
358
+
359
+ const expectedChild = node.schema ? node.schema.element : null;
360
+
361
+ // --- COMPACT MODE ---
362
+ if (this.config.compact) {
363
+ const items: string[] = [];
364
+
365
+ for (const el of node.elements) {
366
+ // IMPORTANT: We disable schema inclusion for elements to avoid duplication <...>
367
+ // unless types mismatch drastically.
368
+ let val = this.encode(el, 0, false).trim();
369
+
370
+ // Check compatibility & Inject override if needed
371
+ if (!this.schemasAreCompatible(el.schema, expectedChild)) {
372
+ const label = el.schema ? this.getTypeLabel(el.schema) : "any";
373
+ const tag = this.c(`<${label}>`, Colors.SCHEMA);
374
+ val = `${tag} ${val}`;
375
+ }
376
+
377
+ items.push(val);
378
+ }
379
+
380
+ return ind + "[" + innerMeta + schemaHeader + items.join(",") + "]";
381
+ }
382
+
383
+ // --- PRETTY MODE ---
384
+ const header = this.listHeader(node);
385
+ const out: string[] = [ind + header];
386
+
387
+ if (innerMeta) {
388
+ out.push(" ".repeat(childIndent) + innerMeta.trim());
389
+ }
390
+
391
+ if (schemaHeader) {
392
+ out.push(" ".repeat(childIndent) + schemaHeader.trim());
393
+ }
394
+
395
+ for (const el of node.elements) {
396
+ // IMPORTANT: Disable schema for children
397
+ let val = this.encode(
398
+ el, childIndent - this.config.startIndent, false
399
+ ).trim();
400
+
401
+ // Check compatibility & Inject override if needed
402
+ if (!this.schemasAreCompatible(el.schema, expectedChild)) {
403
+ const label = el.schema ? this.getTypeLabel(el.schema) : "any";
404
+ const tag = this.c(`<${label}>`, Colors.SCHEMA);
405
+ val = `${tag} ${val}`;
406
+ }
407
+
408
+ out.push(" ".repeat(childIndent) + val);
409
+ }
410
+
411
+ out.push(ind + "]");
412
+ return this.joinLines(out, "\n");
413
+ }
414
+
415
+
416
+ // -------------------------------------------------------------
417
+ // RECORD
418
+ // -------------------------------------------------------------
419
+ private encodeRecord(node: Node, indent: number): string {
420
+ const innerMeta = this.metaWrapped(node);
421
+
422
+ const parts: string[] = [];
423
+ if (node.schema.fields && node.schema.fields.length > 0) {
424
+ for (const fieldDef of node.schema.fields) {
425
+ const fieldNode = node.fields[fieldDef.name];
426
+ if (fieldNode) {
427
+ let val = this.encode(
428
+ fieldNode, indent - this.config.startIndent, false
429
+ ).trim();
430
+ val = this.applyTypeTag(val, fieldNode.schema, fieldDef);
431
+ parts.push(val);
432
+ } else {
433
+ parts.push(this.c("null", Colors.NULL));
434
+ }
435
+ }
436
+ const sep = this.config.compact ? "," : ", ";
437
+ return "(" + innerMeta + parts.join(sep) + ")";
438
+ } else {
439
+ return "(" + innerMeta + this.c("null", Colors.NULL) + ")";
440
+ }
441
+
442
+ }
443
+ }
@@ -0,0 +1,150 @@
1
+ import { Node } from '../models/Node';
2
+ import { Schema, SchemaKind } from '../models/Schema';
3
+
4
+ export class EncodingError extends Error {
5
+ constructor(message: string) {
6
+ super(message);
7
+ this.name = "EncodingError";
8
+ }
9
+ }
10
+
11
+ // --------------------------------------------------------------
12
+ // Helper: Type Wrapper
13
+ // --------------------------------------------------------------
14
+ const TYPE_STRING = "string";
15
+ const TYPE_NUMBER = "number";
16
+ const TYPE_BOOL = "bool";
17
+ const TYPE_NULL = "null";
18
+ // const TYPE_ANY = "any";
19
+
20
+ // --------------------------------------------------------------
21
+ // Primitive -> Node(primitive)
22
+ // --------------------------------------------------------------
23
+
24
+ function parsePrimitive(v: any): Node {
25
+ let schema: Schema;
26
+
27
+ if (typeof v === 'string') {
28
+ schema = new Schema(SchemaKind.PRIMITIVE, { typeName: TYPE_STRING });
29
+ } else if (typeof v === 'boolean') {
30
+ schema = new Schema(SchemaKind.PRIMITIVE, { typeName: TYPE_BOOL });
31
+ } else if (typeof v === 'number') {
32
+ schema = new Schema(SchemaKind.PRIMITIVE, { typeName: TYPE_NUMBER });
33
+ } else if (v === null || v === undefined) {
34
+ schema = new Schema(SchemaKind.PRIMITIVE, { typeName: TYPE_NULL });
35
+ } else {
36
+ throw new EncodingError(`Unsupported primitive: ${v}`);
37
+ }
38
+
39
+ return new Node(schema, { value: v });
40
+ }
41
+
42
+ // --------------------------------------------------------------
43
+ // List -> Node(list)
44
+ // --------------------------------------------------------------
45
+
46
+ function parseList(arr: any[]): Node {
47
+ // 1. EMPTY LIST
48
+ if (arr.length === 0) {
49
+ const elementSchema = new Schema(SchemaKind.PRIMITIVE, { typeName: "any" });
50
+ const listSchema = new Schema(SchemaKind.LIST, { element: elementSchema });
51
+ return new Node(listSchema, { elements: [] });
52
+ }
53
+
54
+ // 2. PARSE ALL ITEMS
55
+ const parsedItems: Node[] = arr.map(v => parseDataToNode(v));
56
+
57
+ // Use the first element as the baseline
58
+ const firstSchema = parsedItems[0].schema;
59
+ const isListOfRecords = (firstSchema.kind === SchemaKind.RECORD);
60
+
61
+ let unifiedElementSchema: Schema;
62
+
63
+ // 3. DETERMINE ELEMENT SCHEMA
64
+ if (isListOfRecords) {
65
+ // RECORDS: Create a unified schema containing ALL fields from ALL items.
66
+ unifiedElementSchema = new Schema(SchemaKind.RECORD, { typeName: "record" });
67
+ const seenFields = new Set<string>();
68
+
69
+ for (const item of parsedItems) {
70
+ // Skip non-record items if mixed list (or handle as error depending on strictness)
71
+ if (item.schema.kind === SchemaKind.RECORD) {
72
+ for (const field of item.schema.fields) {
73
+ if (!seenFields.has(field.name)) {
74
+ unifiedElementSchema.addField(field);
75
+ seenFields.add(field.name);
76
+ }
77
+ }
78
+ }
79
+ }
80
+ } else {
81
+ // PRIMITIVES: Simply take the schema of the first element.
82
+ // We assume the list is homogeneous based on the first item.
83
+ unifiedElementSchema = firstSchema;
84
+ }
85
+
86
+ // 4. FINALIZE
87
+ const listSchema = new Schema(SchemaKind.LIST, {
88
+ typeName: "list",
89
+ element: unifiedElementSchema
90
+ });
91
+
92
+ return new Node(listSchema, { elements: parsedItems });
93
+ }
94
+
95
+ // --------------------------------------------------------------
96
+ // Dict -> Node(record)
97
+ // --------------------------------------------------------------
98
+
99
+ function parseDict(obj: Record<string, any>): Node {
100
+ /**
101
+ * JSON objects -> named records.
102
+ */
103
+ const fieldsData: Record<string, Node> = {};
104
+ const schema = new Schema(SchemaKind.RECORD);
105
+
106
+ for (const [key, rawValue] of Object.entries(obj)) {
107
+ const childNode = parseDataToNode(rawValue);
108
+
109
+ // Assign field name to the child's schema so it knows it is a field
110
+ childNode.schema.name = key;
111
+
112
+ fieldsData[key] = childNode;
113
+ schema.addField(childNode.schema);
114
+ }
115
+
116
+ return new Node(schema, { fields: fieldsData });
117
+ }
118
+
119
+ // --------------------------------------------------------------
120
+ // Main entrypoint
121
+ // --------------------------------------------------------------
122
+
123
+ export function parseDataToNode(value: any): Node {
124
+ if (value instanceof Node) {
125
+ return value;
126
+ }
127
+
128
+ // 1. Primitive Check
129
+ if (
130
+ value === null ||
131
+ value === undefined ||
132
+ typeof value === 'string' ||
133
+ typeof value === 'number' ||
134
+ typeof value === 'boolean'
135
+ ) {
136
+ return parsePrimitive(value);
137
+ }
138
+
139
+ // 2. List Check
140
+ if (Array.isArray(value)) {
141
+ return parseList(value);
142
+ }
143
+
144
+ // 3. Object Check (Dict)
145
+ if (typeof value === 'object') {
146
+ return parseDict(value);
147
+ }
148
+
149
+ throw new EncodingError(`Unsupported structure type: ${typeof value}`);
150
+ }
package/src/index.ts ADDED
@@ -0,0 +1,46 @@
1
+ import { EncoderConfig, DEFAULT_CONFIG } from './config';
2
+ import { Encoder } from './core/Encoder';
3
+ import { Decoder, DecodeResult } from './core/Decoder';
4
+ import { parseDataToNode } from './core/Parser';
5
+ import { Node } from './models/Node';
6
+ import { Schema, SchemaKind } from './models/Schema';
7
+ import { MetaInfo, Meta, MetaProps } from './models/Meta';
8
+
9
+ // Re-export types and classes
10
+ export { Node, Schema, SchemaKind, DecodeResult, EncoderConfig,
11
+ Meta, MetaInfo, MetaProps };
12
+
13
+ // =============================================================
14
+ // PUBLIC API
15
+ // =============================================================
16
+
17
+ /**
18
+ * Encode input data into valid **AI.Data** format.
19
+ */
20
+ export function encode(data: any | Node, config: Partial<EncoderConfig> = {}): string {
21
+ let node: Node;
22
+ node = parseDataToNode(data);
23
+ const finalConfig: EncoderConfig = { ...DEFAULT_CONFIG, ...config };
24
+ const encoder = new Encoder(finalConfig);
25
+ return encoder.encode(node);
26
+ }
27
+
28
+ /**
29
+ * Decode AI.Data format text into a Node structure.
30
+ */
31
+ export function decode(
32
+ text: string,
33
+ options: { removeAnsiColors?: boolean; debug?: boolean } = {},
34
+ schema: string = ""
35
+ ): DecodeResult {
36
+ const { removeAnsiColors = false, debug = false } = options;
37
+ const decoder = new Decoder(text, schema, removeAnsiColors, debug);
38
+ return decoder.decode();
39
+ }
40
+
41
+ /**
42
+ * Convert raw JavaScript object/value into a Node.
43
+ */
44
+ export function parse(data: any): Node {
45
+ return parseDataToNode(data);
46
+ }