@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.
- package/dist/config.d.ts +75 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +28 -0
- package/dist/config.js.map +1 -0
- package/dist/core/Decoder.d.ts +88 -0
- package/dist/core/Decoder.d.ts.map +1 -0
- package/dist/core/Decoder.js +968 -0
- package/dist/core/Decoder.js.map +1 -0
- package/dist/core/Encoder.d.ts +26 -0
- package/dist/core/Encoder.d.ts.map +1 -0
- package/dist/core/Encoder.js +368 -0
- package/dist/core/Encoder.js.map +1 -0
- package/dist/core/Parser.d.ts +6 -0
- package/dist/core/Parser.d.ts.map +1 -0
- package/dist/core/Parser.js +132 -0
- package/dist/core/Parser.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/models/Meta.d.ts +48 -0
- package/dist/models/Meta.d.ts.map +1 -0
- package/dist/models/Meta.js +113 -0
- package/dist/models/Meta.js.map +1 -0
- package/dist/models/Node.d.ts +42 -0
- package/dist/models/Node.d.ts.map +1 -0
- package/dist/models/Node.js +179 -0
- package/dist/models/Node.js.map +1 -0
- package/dist/models/Schema.d.ts +55 -0
- package/dist/models/Schema.d.ts.map +1 -0
- package/dist/models/Schema.js +175 -0
- package/dist/models/Schema.js.map +1 -0
- package/package.json +32 -0
- package/src/config.ts +102 -0
- package/src/core/Decoder.ts +1074 -0
- package/src/core/Encoder.ts +443 -0
- package/src/core/Parser.ts +150 -0
- package/src/index.ts +46 -0
- package/src/models/Meta.ts +135 -0
- package/src/models/Node.ts +212 -0
- package/src/models/Schema.ts +222 -0
- package/tests/00.meta.test.ts +31 -0
- package/tests/00.node.test.ts +54 -0
- package/tests/00.primitive.test.ts +108 -0
- package/tests/00.schema.test.ts +41 -0
- package/tests/01.schema.test.ts +70 -0
- package/tests/02.data.test.ts +89 -0
- package/tests/03.errors.test.ts +71 -0
- package/tests/04.list.test.ts +225 -0
- package/tests/05.record.test.ts +82 -0
- package/tests/06.meta.test.ts +506 -0
- package/tests/utils.ts +69 -0
- package/tsconfig.json +46 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
|
|
2
|
+
export interface MetaProps {
|
|
3
|
+
comments?: string[];
|
|
4
|
+
attr?: Record<string, any>;
|
|
5
|
+
tags?: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Mixin/Base class that adds metadata storage capabilities to Node and Schema.
|
|
10
|
+
*/
|
|
11
|
+
export class Meta {
|
|
12
|
+
comments: string[];
|
|
13
|
+
attr: Record<string, any>;
|
|
14
|
+
tags: string[];
|
|
15
|
+
|
|
16
|
+
constructor({ comments, attr, tags }: MetaProps = {}) {
|
|
17
|
+
this.comments = comments ? [...comments] : [];
|
|
18
|
+
this.attr = attr ? { ...attr } : {};
|
|
19
|
+
this.tags = tags ? [...tags] : [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Clears ALL metadata (comments, attributes, tags).
|
|
24
|
+
*/
|
|
25
|
+
clearCommonMeta(): void {
|
|
26
|
+
this.comments = [];
|
|
27
|
+
this.attr = {};
|
|
28
|
+
this.tags = [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Merges only the common fields (attributes, comments, tags)
|
|
33
|
+
* from a MetaInfo object. Safe for both Node and Schema.
|
|
34
|
+
*/
|
|
35
|
+
applyCommonMeta(info: Meta): void {
|
|
36
|
+
// Append comments
|
|
37
|
+
if (info.comments.length > 0) {
|
|
38
|
+
this.comments.push(...info.comments);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Merge attributes ($key=value)
|
|
42
|
+
if (Object.keys(info.attr).length > 0) {
|
|
43
|
+
Object.assign(this.attr, info.attr);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Append tags
|
|
47
|
+
if (info.tags.length > 0) {
|
|
48
|
+
this.tags.push(...info.tags);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A temporary container (DTO) holding parsed metadata from a / ... / block.
|
|
55
|
+
* It contains BOTH Schema constraints (!required) and Node attributes ($key=val).
|
|
56
|
+
*/
|
|
57
|
+
export class MetaInfo extends Meta {
|
|
58
|
+
required: boolean;
|
|
59
|
+
|
|
60
|
+
constructor(props: MetaProps & { required?: boolean } = {}) {
|
|
61
|
+
super(props);
|
|
62
|
+
this.required = props.required || false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Merges everything from another MetaInfo object, including 'required' flag.
|
|
67
|
+
*/
|
|
68
|
+
applyMeta(info: MetaInfo): void {
|
|
69
|
+
// Apply common fields (comments, attr, tags)
|
|
70
|
+
this.applyCommonMeta(info);
|
|
71
|
+
|
|
72
|
+
// Override required meta (Schema Only)
|
|
73
|
+
if (info.required) {
|
|
74
|
+
this.required = true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Allows usage checks like 'if meta.isEmpty()' to see if any metadata was collected.
|
|
80
|
+
* Equivalent to Python's __bool__.
|
|
81
|
+
*/
|
|
82
|
+
isEmpty(): boolean {
|
|
83
|
+
return (
|
|
84
|
+
this.comments.length === 0 &&
|
|
85
|
+
Object.keys(this.attr).length === 0 &&
|
|
86
|
+
this.tags.length === 0 &&
|
|
87
|
+
!this.required
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Debug representation mimicking the actual ADF format style.
|
|
93
|
+
* Example: <MetaInfo !required #tag $key=val >
|
|
94
|
+
*/
|
|
95
|
+
toString(): string {
|
|
96
|
+
const parts: string[] = [];
|
|
97
|
+
|
|
98
|
+
// 1. Flags
|
|
99
|
+
if (this.required) {
|
|
100
|
+
parts.push("!required");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 2. Tags
|
|
104
|
+
for (const t of this.tags) {
|
|
105
|
+
parts.push(`#${t}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 3. Attributes
|
|
109
|
+
for (const [k, v] of Object.entries(this.attr)) {
|
|
110
|
+
// Simplistic value repr for debug
|
|
111
|
+
let valStr: string;
|
|
112
|
+
if (typeof v === 'string') {
|
|
113
|
+
valStr = `"${v}"`;
|
|
114
|
+
} else {
|
|
115
|
+
valStr = String(v);
|
|
116
|
+
}
|
|
117
|
+
parts.push(`$${k}=${valStr}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 4. Comments (Summary)
|
|
121
|
+
if (this.comments.length > 0) {
|
|
122
|
+
if (this.comments.length === 1) {
|
|
123
|
+
const c = this.comments[0];
|
|
124
|
+
const preview = c.length > 15 ? c.substring(0, 15) + '..' : c;
|
|
125
|
+
parts.push(`/* ${preview} */`);
|
|
126
|
+
} else {
|
|
127
|
+
parts.push(`/* ${this.comments.length} comments */`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const content = parts.join(" ");
|
|
132
|
+
return content ? `<MetaInfo ${content}>` : "<MetaInfo (empty)>";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { Meta, MetaInfo, MetaProps } from './Meta';
|
|
2
|
+
import { Schema } from './Schema';
|
|
3
|
+
|
|
4
|
+
export interface NodeProps extends MetaProps {
|
|
5
|
+
name?: string;
|
|
6
|
+
value?: any;
|
|
7
|
+
fields?: Record<string, Node>;
|
|
8
|
+
elements?: Node[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Canonical runtime data object for AI.DATA.
|
|
13
|
+
* A Node always refers to a Schema which defines how data should be interpreted.
|
|
14
|
+
*/
|
|
15
|
+
export class Node extends Meta {
|
|
16
|
+
schema: Schema;
|
|
17
|
+
name: string;
|
|
18
|
+
value: any;
|
|
19
|
+
fields: Record<string, Node>;
|
|
20
|
+
elements: Node[];
|
|
21
|
+
|
|
22
|
+
constructor(schema: Schema, props: NodeProps = {}) {
|
|
23
|
+
super(props);
|
|
24
|
+
this.schema = schema;
|
|
25
|
+
this.name = props.name || "";
|
|
26
|
+
this.value = props.value ?? null;
|
|
27
|
+
this.fields = props.fields || {};
|
|
28
|
+
this.elements = props.elements ? [...props.elements] : [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// -----------------------------------------------------------
|
|
32
|
+
// Introspection helpers
|
|
33
|
+
// -----------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
get isPrimitive(): boolean { return this.schema && this.schema.isPrimitive; }
|
|
36
|
+
get isRecord(): boolean { return this.schema && this.schema.isRecord; }
|
|
37
|
+
get isList(): boolean { return this.schema && this.schema.isList; }
|
|
38
|
+
|
|
39
|
+
// -----------------------------------------------------------
|
|
40
|
+
// Meta
|
|
41
|
+
// -----------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
clearMeta(): void {
|
|
44
|
+
this.clearCommonMeta();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
applyMeta(info: MetaInfo): void {
|
|
48
|
+
// Applies ALL metadata (common stuff: meta dict, comments)
|
|
49
|
+
this.applyCommonMeta(info);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
// -----------------------------------------------------------
|
|
55
|
+
// Conversion Methods (dict / json)
|
|
56
|
+
// -----------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Recursively converts the Node into a standard JavaScript object/array/primitive.
|
|
60
|
+
* Equivalent to Python's .dict() method.
|
|
61
|
+
*/
|
|
62
|
+
dict(): any {
|
|
63
|
+
if (this.isPrimitive) {
|
|
64
|
+
return this.value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (this.isList) {
|
|
68
|
+
return this.elements.map(element => element.dict());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (this.isRecord) {
|
|
72
|
+
const result: Record<string, any> = {};
|
|
73
|
+
for (const [key, fieldNode] of Object.entries(this.fields)) {
|
|
74
|
+
result[key] = fieldNode.dict();
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return this.value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Converts the Node to a JSON string.
|
|
84
|
+
* * @param indent Number of spaces for indentation.
|
|
85
|
+
* @param colorize If true, applies ANSI colors to keys, strings, numbers, etc.
|
|
86
|
+
*/
|
|
87
|
+
JSON(indent: number = 2, colorize: boolean = false): string {
|
|
88
|
+
// 1. Convert to standard JS object
|
|
89
|
+
const data = this.dict();
|
|
90
|
+
|
|
91
|
+
// 2. Dump to string
|
|
92
|
+
const jsonStr = JSON.stringify(data, null, indent);
|
|
93
|
+
|
|
94
|
+
if (!colorize) {
|
|
95
|
+
return jsonStr;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 3. Apply Colors (Regex Tokenizer)
|
|
99
|
+
// ANSI codes matching the Encoder class
|
|
100
|
+
const C = {
|
|
101
|
+
RESET: "\x1b[0m",
|
|
102
|
+
STRING: "\x1b[92m", // Green
|
|
103
|
+
NUMBER: "\x1b[94m", // Blue
|
|
104
|
+
BOOL: "\x1b[95m", // Magenta
|
|
105
|
+
NULL: "\x1b[90m", // Gray
|
|
106
|
+
KEY: "\x1b[93m" // Yellow
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Regex to capture JSON tokens:
|
|
110
|
+
// Group 1: Keys ("key": )
|
|
111
|
+
// Group 2: String values ("value")
|
|
112
|
+
// Group 3: Booleans/Null
|
|
113
|
+
// Group 4: Numbers
|
|
114
|
+
const tokenPattern = /(".*?"\s*:)|(".*?")|\b(true|false|null)\b|(-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g;
|
|
115
|
+
|
|
116
|
+
return jsonStr.replace(tokenPattern, (match) => {
|
|
117
|
+
// Check based on content patterns
|
|
118
|
+
|
|
119
|
+
// Key: Ends with ':' (ignoring whitespace) and starts with '"'
|
|
120
|
+
if (/^".*":\s*$/.test(match)) {
|
|
121
|
+
return `${C.KEY}${match}${C.RESET}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// String value: Starts with '"'
|
|
125
|
+
if (match.startsWith('"')) {
|
|
126
|
+
return `${C.STRING}${match}${C.RESET}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Boolean
|
|
130
|
+
if (match === "true" || match === "false") {
|
|
131
|
+
return `${C.BOOL}${match}${C.RESET}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Null
|
|
135
|
+
if (match === "null") {
|
|
136
|
+
return `${C.NULL}${match}${C.RESET}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Number (Fallthrough)
|
|
140
|
+
return `${C.NUMBER}${match}${C.RESET}`;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// -----------------------------------------------------------
|
|
145
|
+
// Debug / Representation
|
|
146
|
+
// -----------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Technical debug representation.
|
|
150
|
+
* Format: <Node(KIND:type) value/len=... details...>
|
|
151
|
+
*/
|
|
152
|
+
toString(): string {
|
|
153
|
+
// 1. Type Info
|
|
154
|
+
let typeLabel = "UNKNOWN";
|
|
155
|
+
if (this.schema) {
|
|
156
|
+
const kind = this.schema.kind;
|
|
157
|
+
const typeName = this.schema.typeName;
|
|
158
|
+
|
|
159
|
+
if (this.isList) {
|
|
160
|
+
const elType = this.schema.element ? (this.schema.element.typeName || "any") : "any";
|
|
161
|
+
typeLabel = `LIST[${elType}]`;
|
|
162
|
+
} else if (this.isRecord && typeName !== "record" && typeName !== "any") {
|
|
163
|
+
typeLabel = `RECORD:${typeName}`;
|
|
164
|
+
} else {
|
|
165
|
+
typeLabel = `${kind}:${typeName}`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const header = `<Node(${typeLabel})`;
|
|
170
|
+
const content: string[] = [];
|
|
171
|
+
|
|
172
|
+
// 2. Content Info
|
|
173
|
+
if (this.isPrimitive) {
|
|
174
|
+
let v = String(this.value);
|
|
175
|
+
if (typeof this.value === 'string') v = `"${v}"`;
|
|
176
|
+
if (v.length > 50) v = v.substring(0, 47) + "...";
|
|
177
|
+
content.push(`val=${v}`);
|
|
178
|
+
} else if (this.isList) {
|
|
179
|
+
content.push(`len=${this.elements.length}`);
|
|
180
|
+
} else if (this.isRecord) {
|
|
181
|
+
const keys = Object.keys(this.fields);
|
|
182
|
+
let keysStr = "";
|
|
183
|
+
if (keys.length > 3) {
|
|
184
|
+
keysStr = keys.slice(0, 3).join(", ") + ", ...";
|
|
185
|
+
} else {
|
|
186
|
+
keysStr = keys.join(", ");
|
|
187
|
+
}
|
|
188
|
+
content.push(`fields=[${keysStr}]`);
|
|
189
|
+
} else {
|
|
190
|
+
// Fallback
|
|
191
|
+
let v = String(this.value);
|
|
192
|
+
if (v.length > 50) v = v.substring(0, 47) + "...";
|
|
193
|
+
content.push(`val=${v}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 3. Meta Indicators
|
|
197
|
+
if (this.comments.length > 0) {
|
|
198
|
+
content.push(`comments=${this.comments.length}`);
|
|
199
|
+
}
|
|
200
|
+
if (Object.keys(this.attr).length > 0) {
|
|
201
|
+
content.push(`attr=[${Object.keys(this.attr).join(', ')}]`);
|
|
202
|
+
}
|
|
203
|
+
if (this.tags.length > 0) {
|
|
204
|
+
content.push(`tags=[${this.tags.join(', ')}]`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const detailsStr = content.length > 0 ? " " + content.join(" ") : "";
|
|
208
|
+
return `${header}${detailsStr}>`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { Meta, MetaInfo, MetaProps } from './Meta';
|
|
2
|
+
|
|
3
|
+
export enum SchemaKind {
|
|
4
|
+
PRIMITIVE = "PRIMITIVE", // int, string, bool, null
|
|
5
|
+
RECORD = "RECORD", // User, Point, or anonymous <...>
|
|
6
|
+
LIST = "LIST", // Array/Sequence
|
|
7
|
+
DICT = "DICT", // Future-proofing: Key-Value pairs
|
|
8
|
+
ANY = "ANY" // Fallback
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SchemaProps extends MetaProps {
|
|
12
|
+
typeName?: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
fields?: Schema[];
|
|
15
|
+
element?: Schema;
|
|
16
|
+
key?: Schema;
|
|
17
|
+
value?: Schema;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class Schema extends Meta {
|
|
22
|
+
kind: SchemaKind;
|
|
23
|
+
typeName: string;
|
|
24
|
+
name: string;
|
|
25
|
+
|
|
26
|
+
// Structure references
|
|
27
|
+
element: Schema | null;
|
|
28
|
+
key: Schema | null;
|
|
29
|
+
value: Schema | null;
|
|
30
|
+
|
|
31
|
+
// Flags
|
|
32
|
+
required: boolean;
|
|
33
|
+
|
|
34
|
+
// Internal fields storage
|
|
35
|
+
private _fieldsList: Schema[] = [];
|
|
36
|
+
private _fieldsMap: Map<string, Schema> = new Map();
|
|
37
|
+
|
|
38
|
+
constructor(kind: SchemaKind, props: SchemaProps = {}) {
|
|
39
|
+
super(props);
|
|
40
|
+
|
|
41
|
+
this.kind = kind;
|
|
42
|
+
this.typeName = props.typeName || "any";
|
|
43
|
+
this.name = props.name || "";
|
|
44
|
+
|
|
45
|
+
this.element = props.element || null;
|
|
46
|
+
this.key = props.key || null;
|
|
47
|
+
this.value = props.value || null;
|
|
48
|
+
this.required = props.required || false;
|
|
49
|
+
|
|
50
|
+
if (props.fields) {
|
|
51
|
+
props.fields.forEach(f => this.addField(f));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// -----------------------------------------------------------
|
|
56
|
+
// Properties (Is...)
|
|
57
|
+
// -----------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
get isPrimitive(): boolean { return this.kind === SchemaKind.PRIMITIVE; }
|
|
60
|
+
get isRecord(): boolean { return this.kind === SchemaKind.RECORD; }
|
|
61
|
+
get isList(): boolean { return this.kind === SchemaKind.LIST; }
|
|
62
|
+
|
|
63
|
+
get isAny(): boolean {
|
|
64
|
+
return (
|
|
65
|
+
this.kind === SchemaKind.ANY ||
|
|
66
|
+
(this.typeName === "any" && this.kind === SchemaKind.PRIMITIVE) ||
|
|
67
|
+
(this.typeName === "any" && this.kind === SchemaKind.RECORD)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get fields(): Schema[] {
|
|
72
|
+
return this._fieldsList;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// -----------------------------------------------------------
|
|
76
|
+
// Field Management
|
|
77
|
+
// -----------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
clearFields(): void {
|
|
80
|
+
this._fieldsList = [];
|
|
81
|
+
this._fieldsMap.clear();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
addField(field: Schema): void {
|
|
85
|
+
// Python logic: Auto-switch to RECORD if adding fields
|
|
86
|
+
if (this.kind !== SchemaKind.RECORD) {
|
|
87
|
+
this.kind = SchemaKind.RECORD;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Auto-naming if missing
|
|
91
|
+
const fName = field.name || String(this._fieldsList.length);
|
|
92
|
+
field.name = fName;
|
|
93
|
+
|
|
94
|
+
this._fieldsList.push(field);
|
|
95
|
+
this._fieldsMap.set(fName, field);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Equivalent to Python's __getitem__.
|
|
100
|
+
* Allows access by numeric index or field name string.
|
|
101
|
+
*/
|
|
102
|
+
getField(key: number | string): Schema | undefined {
|
|
103
|
+
if (!this.isRecord) {
|
|
104
|
+
throw new Error(`Schema kind ${this.kind} is not subscriptable (not a RECORD).`);
|
|
105
|
+
}
|
|
106
|
+
if (typeof key === 'number') {
|
|
107
|
+
return this._fieldsList[key];
|
|
108
|
+
}
|
|
109
|
+
return this._fieldsMap.get(key);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Replaces an existing field with a new definition based on field.name.
|
|
114
|
+
* Preserves the original order in the fields list.
|
|
115
|
+
* If the field does not exist, it appends it (like addField).
|
|
116
|
+
*/
|
|
117
|
+
replaceField(field: Schema): void {
|
|
118
|
+
const fName = field.name;
|
|
119
|
+
if (!fName) {
|
|
120
|
+
throw new Error("Cannot replace a field without a name.");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (this._fieldsMap.has(fName)) {
|
|
124
|
+
// 1. Retrieve the old object to find its index
|
|
125
|
+
const oldField = this._fieldsMap.get(fName)!;
|
|
126
|
+
const idx = this._fieldsList.indexOf(oldField);
|
|
127
|
+
|
|
128
|
+
if (idx !== -1) {
|
|
129
|
+
// 2. Replace in list (preserve order)
|
|
130
|
+
this._fieldsList[idx] = field;
|
|
131
|
+
} else {
|
|
132
|
+
// Fallback safety (should not happen if map/list synced)
|
|
133
|
+
this._fieldsList.push(field);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 3. Update map
|
|
137
|
+
this._fieldsMap.set(fName, field);
|
|
138
|
+
} else {
|
|
139
|
+
// Field doesn't exist, treat as add
|
|
140
|
+
this.addField(field);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// -----------------------------------------------------------
|
|
145
|
+
// Meta Management
|
|
146
|
+
// -----------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
clearMeta(): void {
|
|
149
|
+
this.clearCommonMeta();
|
|
150
|
+
this.required = false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
applyMeta(info: MetaInfo | Schema | undefined): void {
|
|
154
|
+
if(!info) return;
|
|
155
|
+
// 1. Apply common stuff (meta dict, comments, tags)
|
|
156
|
+
this.applyCommonMeta(info);
|
|
157
|
+
|
|
158
|
+
// 2. Apply Schema-specific constraints
|
|
159
|
+
if (info.required) {
|
|
160
|
+
this.required = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// -----------------------------------------------------------
|
|
165
|
+
// Debug / Representation
|
|
166
|
+
// -----------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Technical debug representation.
|
|
170
|
+
* Format: <Schema(KIND:type_name) name='...' details...>
|
|
171
|
+
*/
|
|
172
|
+
toString(): string {
|
|
173
|
+
// 1. Basic Info: Kind and TypeName
|
|
174
|
+
const kindStr = this.kind;
|
|
175
|
+
|
|
176
|
+
let typeLabel = "";
|
|
177
|
+
if (this.typeName && this.typeName !== "any" && this.typeName !== this.kind) {
|
|
178
|
+
typeLabel = `:${this.typeName}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const header = `<Schema(${kindStr}${typeLabel})`;
|
|
182
|
+
|
|
183
|
+
// 2. Field Name
|
|
184
|
+
const nameStr = this.name ? ` name="${this.name}"` : "";
|
|
185
|
+
|
|
186
|
+
// 3. Details
|
|
187
|
+
const details: string[] = [];
|
|
188
|
+
|
|
189
|
+
if (this.required) details.push("!required");
|
|
190
|
+
|
|
191
|
+
const attrKeys = Object.keys(this.attr);
|
|
192
|
+
if (attrKeys.length > 0) details.push(`attr=[${attrKeys.map(a => '"' + a + '"').join(', ')}]`);
|
|
193
|
+
|
|
194
|
+
if (this.tags.length > 0) details.push(`tags=[${this.tags.join(', ')}]`);
|
|
195
|
+
if (this.comments.length > 0) details.push(`comments=${this.comments.length}`);
|
|
196
|
+
|
|
197
|
+
// Structure: Record
|
|
198
|
+
if (this.isRecord) {
|
|
199
|
+
const count = this._fieldsList.length;
|
|
200
|
+
if (count > 0) {
|
|
201
|
+
const limit = 3;
|
|
202
|
+
const fieldNames = this._fieldsList.slice(0, limit).map(f => f.name);
|
|
203
|
+
if (count > limit) fieldNames.push("...");
|
|
204
|
+
details.push(`fields(${count})=[${fieldNames.join(', ')}]`);
|
|
205
|
+
} else {
|
|
206
|
+
details.push("fields=[]");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Structure: List
|
|
210
|
+
else if (this.isList) {
|
|
211
|
+
const elType = this.element ? (this.element.typeName || "None") : "None";
|
|
212
|
+
const elKind = this.element ? this.element.kind : "ANY";
|
|
213
|
+
details.push(`element=${elKind}:${elType}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const detailsStr = details.length > 0 ? " " + details.join(" ") : "";
|
|
217
|
+
|
|
218
|
+
return `${header}${nameStr}${detailsStr}>`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { MetaInfo } from '../src/index';
|
|
3
|
+
|
|
4
|
+
describe('AI Data Meta', () => {
|
|
5
|
+
it('should Meta be properly formatted', () => {
|
|
6
|
+
const meta = new MetaInfo({
|
|
7
|
+
|
|
8
|
+
comments: ["This is a comment"],
|
|
9
|
+
attr: { foo: "bar" },
|
|
10
|
+
tags: ["tag1", "tag2"],
|
|
11
|
+
required: true
|
|
12
|
+
});
|
|
13
|
+
const expected = '<MetaInfo !required #tag1 #tag2 $foo="bar" /* This is a comme.. */>';
|
|
14
|
+
// const expectedJSON
|
|
15
|
+
const expected_val = {
|
|
16
|
+
"comments": [
|
|
17
|
+
"This is a comment"
|
|
18
|
+
],
|
|
19
|
+
"attr": {
|
|
20
|
+
"foo": "bar"
|
|
21
|
+
},
|
|
22
|
+
"tags": [
|
|
23
|
+
"tag1",
|
|
24
|
+
"tag2"
|
|
25
|
+
],
|
|
26
|
+
"required": true
|
|
27
|
+
}
|
|
28
|
+
expect(meta).toMatchObject(expected_val);
|
|
29
|
+
expect(meta.toString()).toBe(expected);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Schema, SchemaKind, Node } from '../src/index';
|
|
3
|
+
|
|
4
|
+
describe('AI Node test', () => {
|
|
5
|
+
it('should encode raw object', () => {
|
|
6
|
+
const schema = new Schema(
|
|
7
|
+
SchemaKind.DICT,
|
|
8
|
+
{
|
|
9
|
+
name: "TestSchema",
|
|
10
|
+
comments: ["This is a comment"],
|
|
11
|
+
attr: { foo: "bar" },
|
|
12
|
+
tags: ["tag1", "tag2"],
|
|
13
|
+
required: true
|
|
14
|
+
|
|
15
|
+
});
|
|
16
|
+
const node = new Node(schema, {
|
|
17
|
+
value: 3,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const expected = '<Node(DICT:any) val=3>';
|
|
21
|
+
const expected_val = {
|
|
22
|
+
"comments": [],
|
|
23
|
+
"attr": {},
|
|
24
|
+
"tags": [],
|
|
25
|
+
"schema": {
|
|
26
|
+
"comments": [
|
|
27
|
+
"This is a comment"
|
|
28
|
+
],
|
|
29
|
+
"attr": {
|
|
30
|
+
"foo": "bar"
|
|
31
|
+
},
|
|
32
|
+
"tags": [
|
|
33
|
+
"tag1",
|
|
34
|
+
"tag2"
|
|
35
|
+
],
|
|
36
|
+
"_fieldsList": [],
|
|
37
|
+
"_fieldsMap": {},
|
|
38
|
+
"kind": "DICT",
|
|
39
|
+
"typeName": "any",
|
|
40
|
+
"name": "TestSchema",
|
|
41
|
+
"element": null,
|
|
42
|
+
"key": null,
|
|
43
|
+
"value": null,
|
|
44
|
+
"required": true
|
|
45
|
+
},
|
|
46
|
+
"name": "",
|
|
47
|
+
"value": 3,
|
|
48
|
+
"fields": {},
|
|
49
|
+
"elements": []
|
|
50
|
+
}
|
|
51
|
+
expect(node).toMatchObject(expected_val);
|
|
52
|
+
expect(node.toString()).toBe(expected);
|
|
53
|
+
});
|
|
54
|
+
});
|