@arkadia/data 0.1.7 → 0.1.9
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 +159 -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/src/models/Node.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Meta, MetaInfo, MetaProps } from './Meta';
|
|
2
2
|
import { Schema } from './Schema';
|
|
3
3
|
|
|
4
|
+
export type Primitive = string | number | boolean | undefined | null;
|
|
5
|
+
|
|
4
6
|
export interface NodeProps extends MetaProps {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
name?: string;
|
|
8
|
+
value?: unknown;
|
|
9
|
+
fields?: Record<string, Node>;
|
|
10
|
+
elements?: Node[];
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -13,200 +15,203 @@ export interface NodeProps extends MetaProps {
|
|
|
13
15
|
* A Node always refers to a Schema which defines how data should be interpreted.
|
|
14
16
|
*/
|
|
15
17
|
export class Node extends Meta {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
schema: Schema;
|
|
19
|
+
name: string;
|
|
20
|
+
value: unknown;
|
|
21
|
+
fields: Record<string, Node>;
|
|
22
|
+
elements: Node[];
|
|
23
|
+
|
|
24
|
+
constructor(schema: Schema, props: NodeProps = {}) {
|
|
25
|
+
super(props);
|
|
26
|
+
this.schema = schema;
|
|
27
|
+
this.name = props.name || '';
|
|
28
|
+
this.value = props.value ?? null;
|
|
29
|
+
this.fields = props.fields || {};
|
|
30
|
+
this.elements = props.elements ? [...props.elements] : [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// -----------------------------------------------------------
|
|
34
|
+
// Introspection helpers
|
|
35
|
+
// -----------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
get isPrimitive(): boolean {
|
|
38
|
+
return this.schema && this.schema.isPrimitive;
|
|
39
|
+
}
|
|
40
|
+
get isRecord(): boolean {
|
|
41
|
+
return this.schema && this.schema.isRecord;
|
|
42
|
+
}
|
|
43
|
+
get isList(): boolean {
|
|
44
|
+
return this.schema && this.schema.isList;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// -----------------------------------------------------------
|
|
48
|
+
// Meta
|
|
49
|
+
// -----------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
clearMeta(): void {
|
|
52
|
+
this.clearCommonMeta();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
applyMeta(info: MetaInfo): void {
|
|
56
|
+
// Applies ALL metadata (common stuff: meta dict, comments)
|
|
57
|
+
this.applyCommonMeta(info);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// -----------------------------------------------------------
|
|
61
|
+
// Conversion Methods (dict / json)
|
|
62
|
+
// -----------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Recursively converts the Node into a standard JavaScript object/array/primitive.
|
|
66
|
+
* Equivalent to Python's .dict() method.
|
|
67
|
+
*/
|
|
68
|
+
dict(): unknown {
|
|
69
|
+
if (this.isPrimitive) {
|
|
70
|
+
return this.value;
|
|
29
71
|
}
|
|
30
72
|
|
|
31
|
-
|
|
32
|
-
|
|
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();
|
|
73
|
+
if (this.isList) {
|
|
74
|
+
return this.elements.map((element) => element.dict());
|
|
45
75
|
}
|
|
46
76
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
77
|
+
if (this.isRecord) {
|
|
78
|
+
const result: Record<string, unknown> = {};
|
|
79
|
+
for (const [key, fieldNode] of Object.entries(this.fields)) {
|
|
80
|
+
result[key] = fieldNode.dict();
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
50
83
|
}
|
|
51
84
|
|
|
85
|
+
return this.value;
|
|
86
|
+
}
|
|
52
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Converts the Node to a JSON string.
|
|
90
|
+
* * @param indent Number of spaces for indentation.
|
|
91
|
+
* @param colorize If true, applies ANSI colors to keys, strings, numbers, etc.
|
|
92
|
+
*/
|
|
93
|
+
JSON(indent: number = 2, colorize: boolean = false): string {
|
|
94
|
+
// 1. Convert to standard JS object
|
|
95
|
+
const data = this.dict();
|
|
53
96
|
|
|
54
|
-
//
|
|
55
|
-
|
|
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
|
-
}
|
|
97
|
+
// 2. Dump to string
|
|
98
|
+
const jsonStr = JSON.stringify(data, null, indent);
|
|
66
99
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
100
|
+
if (!colorize) {
|
|
101
|
+
return jsonStr;
|
|
102
|
+
}
|
|
70
103
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
104
|
+
// 3. Apply Colors (Regex Tokenizer)
|
|
105
|
+
// ANSI codes matching the Encoder class
|
|
106
|
+
const C = {
|
|
107
|
+
RESET: '\x1b[0m',
|
|
108
|
+
STRING: '\x1b[92m', // Green
|
|
109
|
+
NUMBER: '\x1b[94m', // Blue
|
|
110
|
+
BOOL: '\x1b[95m', // Magenta
|
|
111
|
+
NULL: '\x1b[90m', // Gray
|
|
112
|
+
KEY: '\x1b[93m', // Yellow
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Regex to capture JSON tokens:
|
|
116
|
+
// Group 1: Keys ("key": )
|
|
117
|
+
// Group 2: String values ("value")
|
|
118
|
+
// Group 3: Booleans/Null
|
|
119
|
+
// Group 4: Numbers
|
|
120
|
+
const tokenPattern =
|
|
121
|
+
/(".*?"\s*:)|(".*?")|\b(true|false|null)\b|(-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g;
|
|
122
|
+
|
|
123
|
+
return jsonStr.replace(tokenPattern, (match) => {
|
|
124
|
+
// Check based on content patterns
|
|
125
|
+
|
|
126
|
+
// Key: Ends with ':' (ignoring whitespace) and starts with '"'
|
|
127
|
+
if (/^".*":\s*$/.test(match)) {
|
|
128
|
+
return `${C.KEY}${match}${C.RESET}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// String value: Starts with '"'
|
|
132
|
+
if (match.startsWith('"')) {
|
|
133
|
+
return `${C.STRING}${match}${C.RESET}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Boolean
|
|
137
|
+
if (match === 'true' || match === 'false') {
|
|
138
|
+
return `${C.BOOL}${match}${C.RESET}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Null
|
|
142
|
+
if (match === 'null') {
|
|
143
|
+
return `${C.NULL}${match}${C.RESET}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Number (Fallthrough)
|
|
147
|
+
return `${C.NUMBER}${match}${C.RESET}`;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// -----------------------------------------------------------
|
|
152
|
+
// Debug / Representation
|
|
153
|
+
// -----------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Technical debug representation.
|
|
157
|
+
* Format: <Node(KIND:type) value/len=... details...>
|
|
158
|
+
*/
|
|
159
|
+
toString(): string {
|
|
160
|
+
// 1. Type Info
|
|
161
|
+
let typeLabel = 'UNKNOWN';
|
|
162
|
+
if (this.schema) {
|
|
163
|
+
const kind = this.schema.kind;
|
|
164
|
+
const typeName = this.schema.typeName;
|
|
165
|
+
|
|
166
|
+
if (this.isList) {
|
|
167
|
+
const elType = this.schema.element ? this.schema.element.typeName || 'any' : 'any';
|
|
168
|
+
typeLabel = `LIST[${elType}]`;
|
|
169
|
+
} else if (this.isRecord && typeName !== 'record' && typeName !== 'any') {
|
|
170
|
+
typeLabel = `RECORD:${typeName}`;
|
|
171
|
+
} else {
|
|
172
|
+
typeLabel = `${kind}:${typeName}`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
78
175
|
|
|
79
|
-
|
|
176
|
+
const header = `<Node(${typeLabel})`;
|
|
177
|
+
const content: string[] = [];
|
|
178
|
+
|
|
179
|
+
// 2. Content Info
|
|
180
|
+
if (this.isPrimitive) {
|
|
181
|
+
let v = String(this.value);
|
|
182
|
+
if (typeof this.value === 'string') v = `"${v}"`;
|
|
183
|
+
if (v.length > 50) v = v.substring(0, 47) + '...';
|
|
184
|
+
content.push(`val=${v}`);
|
|
185
|
+
} else if (this.isList) {
|
|
186
|
+
content.push(`len=${this.elements.length}`);
|
|
187
|
+
} else if (this.isRecord) {
|
|
188
|
+
const keys = Object.keys(this.fields);
|
|
189
|
+
let keysStr = '';
|
|
190
|
+
if (keys.length > 3) {
|
|
191
|
+
keysStr = keys.slice(0, 3).join(', ') + ', ...';
|
|
192
|
+
} else {
|
|
193
|
+
keysStr = keys.join(', ');
|
|
194
|
+
}
|
|
195
|
+
content.push(`fields=[${keysStr}]`);
|
|
196
|
+
} else {
|
|
197
|
+
// Fallback
|
|
198
|
+
let v = String(this.value);
|
|
199
|
+
if (v.length > 50) v = v.substring(0, 47) + '...';
|
|
200
|
+
content.push(`val=${v}`);
|
|
80
201
|
}
|
|
81
202
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
});
|
|
203
|
+
// 3. Meta Indicators
|
|
204
|
+
if (this.comments.length > 0) {
|
|
205
|
+
content.push(`comments=${this.comments.length}`);
|
|
142
206
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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}>`;
|
|
207
|
+
if (Object.keys(this.attr).length > 0) {
|
|
208
|
+
content.push(`attr=[${Object.keys(this.attr).join(', ')}]`);
|
|
209
|
+
}
|
|
210
|
+
if (this.tags.length > 0) {
|
|
211
|
+
content.push(`tags=[${this.tags.join(', ')}]`);
|
|
209
212
|
}
|
|
210
213
|
|
|
211
|
-
|
|
212
|
-
}
|
|
214
|
+
const detailsStr = content.length > 0 ? ' ' + content.join(' ') : '';
|
|
215
|
+
return `${header}${detailsStr}>`;
|
|
216
|
+
}
|
|
217
|
+
}
|