binary-structures-values 0.1.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/cjs/bit-stream-reader.d.ts +33 -0
- package/dist/cjs/bit-stream-reader.js +340 -0
- package/dist/cjs/bit-stream-writer.d.ts +36 -0
- package/dist/cjs/bit-stream-writer.js +336 -0
- package/dist/cjs/compiler-old.d.ts +9 -0
- package/dist/cjs/compiler-old.js +268 -0
- package/dist/cjs/data-stream-reader.d.ts +23 -0
- package/dist/cjs/data-stream-reader.js +132 -0
- package/dist/cjs/data-stream-writer.d.ts +22 -0
- package/dist/cjs/data-stream-writer.js +179 -0
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.js +21 -0
- package/dist/cjs/schema-structure.d.ts +54 -0
- package/dist/cjs/schema-structure.js +273 -0
- package/dist/esm/bit-stream-reader.d.ts +33 -0
- package/dist/esm/bit-stream-reader.js +336 -0
- package/dist/esm/bit-stream-writer.d.ts +36 -0
- package/dist/esm/bit-stream-writer.js +335 -0
- package/dist/esm/compiler-old.d.ts +9 -0
- package/dist/esm/compiler-old.js +264 -0
- package/dist/esm/data-stream-reader.d.ts +23 -0
- package/dist/esm/data-stream-reader.js +128 -0
- package/dist/esm/data-stream-writer.d.ts +22 -0
- package/dist/esm/data-stream-writer.js +175 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/schema-structure.d.ts +54 -0
- package/dist/esm/schema-structure.js +267 -0
- package/package.json +38 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { BitStreamReader } from "./bit-stream-reader";
|
|
2
|
+
import { decodeSchemaString, encodeSchema } from "./schema-structure";
|
|
3
|
+
import { compileReader } from "./compiler-old";
|
|
4
|
+
export class DataStreamReader extends BitStreamReader {
|
|
5
|
+
constructor(schema_or_data, data_or_complete, isComplete) {
|
|
6
|
+
let initialData;
|
|
7
|
+
let schema;
|
|
8
|
+
if (schema_or_data) {
|
|
9
|
+
if (schema_or_data instanceof Uint8Array) {
|
|
10
|
+
initialData = schema_or_data;
|
|
11
|
+
isComplete = !!data_or_complete;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
if (typeof schema_or_data == 'string')
|
|
15
|
+
[schema] = decodeSchemaString(schema_or_data);
|
|
16
|
+
else
|
|
17
|
+
[schema] = decodeSchemaString(encodeSchema(schema_or_data));
|
|
18
|
+
if (data_or_complete) {
|
|
19
|
+
if (data_or_complete instanceof Uint8Array) {
|
|
20
|
+
initialData = data_or_complete;
|
|
21
|
+
isComplete = !!isComplete;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
super(initialData, isComplete);
|
|
27
|
+
this.schema = this.schema ?? schema;
|
|
28
|
+
}
|
|
29
|
+
static compile(schema) {
|
|
30
|
+
return compileReader(schema);
|
|
31
|
+
}
|
|
32
|
+
readCompiled(compiledFn) {
|
|
33
|
+
return compiledFn(this);
|
|
34
|
+
}
|
|
35
|
+
addData(data) {
|
|
36
|
+
super.addData(data);
|
|
37
|
+
if (this.metaData === undefined && this.canReadString())
|
|
38
|
+
this.metaData = this.readString();
|
|
39
|
+
let schemaString;
|
|
40
|
+
if (!this.schema && this.canReadString() && (schemaString = this.readString())) {
|
|
41
|
+
const [schema, meta] = decodeSchemaString(schemaString);
|
|
42
|
+
this.schema = schema;
|
|
43
|
+
this.metaData = this.metaData || meta;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
isReadyToRead() {
|
|
47
|
+
return this.metaData !== undefined && !!this.schema;
|
|
48
|
+
}
|
|
49
|
+
// if not enough bytes available to read, throws error if isComplete otherwise returns null
|
|
50
|
+
read() {
|
|
51
|
+
if (!this.schema)
|
|
52
|
+
throw new Error('Schema not found');
|
|
53
|
+
if (this.getAvailableBits() <= 0)
|
|
54
|
+
return null;
|
|
55
|
+
const savedPosition = this.bitPosition;
|
|
56
|
+
try {
|
|
57
|
+
return this.readObject(this.schema);
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
this.bitPosition = savedPosition;
|
|
61
|
+
if (!this.isDataComplete()) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
readObject(schema) {
|
|
68
|
+
const result = {};
|
|
69
|
+
for (const key of Object.keys(schema)) {
|
|
70
|
+
const def = schema[key];
|
|
71
|
+
const isOptional = ('optional' in def && def.optional) || ('types' in def &&
|
|
72
|
+
typeof def.types[0] === 'object' && 'optional' in def.types[0] && def.types[0].optional);
|
|
73
|
+
if (isOptional && !this.readBit()) {
|
|
74
|
+
if ('default' in def && def.default !== undefined)
|
|
75
|
+
result[key] = def.default;
|
|
76
|
+
else if ('types' in def && typeof def.types[0] === 'object' && 'default' in def.types[0] && def.types[0].default !== undefined)
|
|
77
|
+
result[key] = def.types[0].default;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
result[key] = 'types' in def ? this.readMultiType(def) : this.readSingleType(def);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
readSingleType(def) {
|
|
85
|
+
if (def.type === 'null')
|
|
86
|
+
return null;
|
|
87
|
+
if (def.type === 'boolean')
|
|
88
|
+
return this.readBit();
|
|
89
|
+
if (def.type === 'string')
|
|
90
|
+
return this.readString();
|
|
91
|
+
if (def.type === 'int-8')
|
|
92
|
+
return this.readInt8();
|
|
93
|
+
if (def.type === 'int-16')
|
|
94
|
+
return this.readInt16();
|
|
95
|
+
if (def.type === 'int-32')
|
|
96
|
+
return this.readInt32();
|
|
97
|
+
if (def.type === 'bigint')
|
|
98
|
+
return this.readBigInt();
|
|
99
|
+
if (def.type === 'float-32')
|
|
100
|
+
return this.readFloat32();
|
|
101
|
+
if (def.type === 'float-64')
|
|
102
|
+
return this.readFloat64();
|
|
103
|
+
if (def.type === 'array')
|
|
104
|
+
return this.readArray(def.items);
|
|
105
|
+
if (def.type === 'object')
|
|
106
|
+
return this.readObject(def.items);
|
|
107
|
+
throw new Error(`Invalid type: ${def.type}`);
|
|
108
|
+
}
|
|
109
|
+
readMultiType(def) {
|
|
110
|
+
const noOfDefs = def.types.length;
|
|
111
|
+
const minBits = Math.ceil(Math.log2(noOfDefs));
|
|
112
|
+
const bits = this.readBits(minBits);
|
|
113
|
+
const typeIndex = bits.reduce((acc, bit, i) => acc | (bit ? 1 << i : 0), 0);
|
|
114
|
+
let selectedDef = def.types[typeIndex];
|
|
115
|
+
if (typeof selectedDef === 'string')
|
|
116
|
+
selectedDef = { type: selectedDef };
|
|
117
|
+
return this.readSingleType(selectedDef);
|
|
118
|
+
}
|
|
119
|
+
readArray(itemDef) {
|
|
120
|
+
const length = this.readInt32();
|
|
121
|
+
const items = [];
|
|
122
|
+
for (let i = 0; i < length; i++) {
|
|
123
|
+
const item = 'types' in itemDef ? this.readMultiType(itemDef) : this.readSingleType(itemDef);
|
|
124
|
+
items.push(item);
|
|
125
|
+
}
|
|
126
|
+
return items;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { BitStreamWriter } from "./bit-stream-writer";
|
|
2
|
+
import { DataType, DataTypeValue, DataValue, MultiType, NonOptionalSingleType, SchemaBase, SingleType } from "./schema-structure";
|
|
3
|
+
export declare class DataStreamWriter extends BitStreamWriter {
|
|
4
|
+
protected readonly schema: SchemaBase;
|
|
5
|
+
protected readonly schemaString: string;
|
|
6
|
+
constructor(schema: SchemaBase, includeSchemaInData: boolean, metaData?: string);
|
|
7
|
+
static compile(schema: SchemaBase): string;
|
|
8
|
+
writeCompiled(compiledFn: (writer: DataStreamWriter, data: any) => void, data: any): void;
|
|
9
|
+
intToBits(int: number, bitCount: number): (0 | 1)[];
|
|
10
|
+
protected writeArray(value: DataTypeValue[], def: {
|
|
11
|
+
type: "array";
|
|
12
|
+
items: DataType;
|
|
13
|
+
optional?: boolean;
|
|
14
|
+
} | ({
|
|
15
|
+
type: "array";
|
|
16
|
+
items: DataType;
|
|
17
|
+
})): void;
|
|
18
|
+
protected writeSingleType(value: DataTypeValue, def: NonOptionalSingleType | SingleType): void;
|
|
19
|
+
protected writeMultiType(value: DataTypeValue, def: MultiType): void;
|
|
20
|
+
protected writeObject(data: DataValue, schema: SchemaBase): void;
|
|
21
|
+
write<D extends DataValue>(data: D): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { BitStreamWriter } from "./bit-stream-writer";
|
|
2
|
+
import { decodeSchemaString, encodeSchema } from "./schema-structure";
|
|
3
|
+
import { compileWriter } from "./compiler-old";
|
|
4
|
+
// int8
|
|
5
|
+
const INT8_MIN = Math.floor(-0xFF / 2);
|
|
6
|
+
const INT8_MAX = Math.floor(0xFF / 2);
|
|
7
|
+
// int16
|
|
8
|
+
const INT16_MIN = Math.floor(-0xFFFF / 2);
|
|
9
|
+
const INT16_MAX = Math.floor(0xFFFF / 2);
|
|
10
|
+
// int32
|
|
11
|
+
const INT32_MIN = Math.floor(-0xFFFFFFFF / 2);
|
|
12
|
+
const INT32_MAX = Math.floor(0xFFFFFFFF / 2);
|
|
13
|
+
export class DataStreamWriter extends BitStreamWriter {
|
|
14
|
+
constructor(schema, includeSchemaInData, metaData = '') {
|
|
15
|
+
super();
|
|
16
|
+
this.schemaString = encodeSchema(schema);
|
|
17
|
+
this.writeString(metaData);
|
|
18
|
+
if (includeSchemaInData)
|
|
19
|
+
this.writeString(this.schemaString);
|
|
20
|
+
else
|
|
21
|
+
this.writeString('');
|
|
22
|
+
this.schema = decodeSchemaString(this.schemaString)[0];
|
|
23
|
+
}
|
|
24
|
+
static compile(schema) {
|
|
25
|
+
return compileWriter(schema);
|
|
26
|
+
}
|
|
27
|
+
writeCompiled(compiledFn, data) {
|
|
28
|
+
compiledFn(this, data);
|
|
29
|
+
}
|
|
30
|
+
intToBits(int, bitCount) {
|
|
31
|
+
// TODO: in stead directly write in loop
|
|
32
|
+
const bits = [];
|
|
33
|
+
for (let i = 0; i < bitCount; i++) {
|
|
34
|
+
bits.push((int & (1 << i)) !== 0 ? 1 : 0);
|
|
35
|
+
}
|
|
36
|
+
return bits;
|
|
37
|
+
}
|
|
38
|
+
writeArray(value, def) {
|
|
39
|
+
const itemDef = def.items;
|
|
40
|
+
this.writeInt32(value.length);
|
|
41
|
+
if ('types' in itemDef) {
|
|
42
|
+
for (let i = 0; i < value.length; i++)
|
|
43
|
+
this.writeMultiType(value[i], itemDef);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
for (let i = 0; i < value.length; i++)
|
|
47
|
+
this.writeSingleType(value[i], itemDef);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
writeSingleType(value, def) {
|
|
51
|
+
if (def.type === 'null' && value === null)
|
|
52
|
+
return;
|
|
53
|
+
if (def.type === 'boolean' && typeof value === 'boolean')
|
|
54
|
+
return this.writeBit(value ? 1 : 0);
|
|
55
|
+
if (def.type === 'string' && typeof value === 'string')
|
|
56
|
+
return this.writeString(value);
|
|
57
|
+
if (def.type === 'int-8' && typeof value === 'number')
|
|
58
|
+
return this.writeInt8(value);
|
|
59
|
+
if (def.type === 'int-16' && typeof value === 'number')
|
|
60
|
+
return this.writeInt16(value);
|
|
61
|
+
if (def.type === 'int-32' && typeof value === 'number')
|
|
62
|
+
return this.writeInt32(value);
|
|
63
|
+
if (def.type === 'bigint' && typeof value === 'bigint')
|
|
64
|
+
return this.writeBigInt(value);
|
|
65
|
+
if (def.type === 'float-32' && typeof value === 'number')
|
|
66
|
+
return this.writeFloat32(value);
|
|
67
|
+
if (def.type === 'float-64' && typeof value === 'number')
|
|
68
|
+
return this.writeFloat64(value);
|
|
69
|
+
if (def.type === 'array' && value !== null && typeof value === 'object' && Array.isArray(value))
|
|
70
|
+
return this.writeArray(value, def);
|
|
71
|
+
if (def.type === 'object' && value !== null && typeof value === 'object' && !Array.isArray(value))
|
|
72
|
+
return this.writeObject(value, def.items);
|
|
73
|
+
throw new Error(`Invalid type: ${def.type}, Value: ${value}`);
|
|
74
|
+
}
|
|
75
|
+
writeMultiType(value, def) {
|
|
76
|
+
let matchedTypes = [];
|
|
77
|
+
const noOfDefs = def.types.length;
|
|
78
|
+
const allTypes = def.types.map(t => typeof t === 'string' ? { type: t } : t);
|
|
79
|
+
// calculate min bits required to store type index
|
|
80
|
+
const minBits = Math.ceil(Math.log2(noOfDefs));
|
|
81
|
+
for (let i = 0; i < noOfDefs; i++) {
|
|
82
|
+
let typeDef = allTypes[i];
|
|
83
|
+
if (typeDef.type === 'null' && value === null)
|
|
84
|
+
matchedTypes.push(i);
|
|
85
|
+
else if (typeDef.type === 'boolean' && typeof value === 'boolean')
|
|
86
|
+
matchedTypes.push(i);
|
|
87
|
+
else if (typeDef.type === 'string' && typeof value === 'string')
|
|
88
|
+
matchedTypes.push(i);
|
|
89
|
+
else if (typeDef.type === 'int-8' && typeof value === 'number' && value % 1 === 0 && value >= INT8_MIN && value <= INT8_MAX)
|
|
90
|
+
matchedTypes.push(i);
|
|
91
|
+
else if (typeDef.type === 'int-16' && typeof value === 'number' && value % 1 === 0 && value >= INT16_MIN && value <= INT16_MAX)
|
|
92
|
+
matchedTypes.push(i);
|
|
93
|
+
else if (typeDef.type === 'int-32' && typeof value === 'number' && value % 1 === 0 && value >= INT32_MIN && value <= INT32_MAX)
|
|
94
|
+
matchedTypes.push(i);
|
|
95
|
+
else if (typeDef.type === 'bigint' && typeof value === 'bigint')
|
|
96
|
+
matchedTypes.push(i);
|
|
97
|
+
else if (typeDef.type === 'float-32' && typeof value === 'number' && Math.abs(value) <= 3.4028235e38)
|
|
98
|
+
matchedTypes.push(i);
|
|
99
|
+
else if (typeDef.type === 'float-64' && typeof value === 'number')
|
|
100
|
+
matchedTypes.push(i);
|
|
101
|
+
else if (typeDef.type === 'array' && value !== null && typeof value === 'object' && Array.isArray(value))
|
|
102
|
+
matchedTypes.push(i);
|
|
103
|
+
else if (typeDef.type === 'object' && value !== null && typeof value === 'object') {
|
|
104
|
+
const allKeys = Object.keys(typeDef.items);
|
|
105
|
+
const optionalKeys = allKeys.filter(key => {
|
|
106
|
+
const itemDef = typeDef.items[key];
|
|
107
|
+
return ('optional' in itemDef && itemDef.optional) || ('types' in itemDef &&
|
|
108
|
+
typeof itemDef.types[0] === 'object' && 'optional' in itemDef.types[0] && itemDef.types[0].optional);
|
|
109
|
+
});
|
|
110
|
+
const requiredKeys = allKeys.filter(key => {
|
|
111
|
+
const itemDef = typeDef.items[key];
|
|
112
|
+
return !(('optional' in itemDef && itemDef.optional) || ('types' in itemDef &&
|
|
113
|
+
typeof itemDef.types[0] === 'object' && 'optional' in itemDef.types[0] && itemDef.types[0].optional));
|
|
114
|
+
});
|
|
115
|
+
const valueKeys = Object.keys(value);
|
|
116
|
+
// 1. has all required keys
|
|
117
|
+
if (!requiredKeys.every(key => valueKeys.includes(key)))
|
|
118
|
+
continue;
|
|
119
|
+
const remainingValueKeys = valueKeys.filter(key => !requiredKeys.includes(key));
|
|
120
|
+
// 2. The remaining keys are in optional (no extra key allowed)
|
|
121
|
+
if (!remainingValueKeys.every(key => optionalKeys.includes(key)))
|
|
122
|
+
continue;
|
|
123
|
+
matchedTypes.push(i);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (matchedTypes.length > 1) {
|
|
127
|
+
if (typeof value === "number") {
|
|
128
|
+
// use the least number of bytes when available
|
|
129
|
+
if (matchedTypes.some(i => allTypes[i].type === 'int-8'))
|
|
130
|
+
matchedTypes = matchedTypes.filter(i => allTypes[i].type === 'int-8');
|
|
131
|
+
else if (matchedTypes.some(i => allTypes[i].type === 'int-16'))
|
|
132
|
+
matchedTypes = matchedTypes.filter(i => allTypes[i].type === 'int-16');
|
|
133
|
+
else if (matchedTypes.some(i => allTypes[i].type === 'int-32'))
|
|
134
|
+
matchedTypes = matchedTypes.filter(i => allTypes[i].type === 'int-32');
|
|
135
|
+
else if (matchedTypes.some(i => allTypes[i].type === 'float-32'))
|
|
136
|
+
matchedTypes = matchedTypes.filter(i => allTypes[i].type === 'float-32');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (matchedTypes.length === 0)
|
|
140
|
+
throw new Error(`No matching type found for value: ${value}, possibles: ${allTypes.map(a => a.type)}`);
|
|
141
|
+
if (matchedTypes.length > 1)
|
|
142
|
+
throw new Error(`Multiple matching types found for value: ${value}, types: ${matchedTypes.map(i => {
|
|
143
|
+
return allTypes[i].type;
|
|
144
|
+
}).join(', ')}`);
|
|
145
|
+
this.writeBits(this.intToBits(matchedTypes[0], minBits));
|
|
146
|
+
let selectedDef = allTypes[matchedTypes[0]];
|
|
147
|
+
return this.writeSingleType(value, selectedDef);
|
|
148
|
+
}
|
|
149
|
+
writeObject(data, schema) {
|
|
150
|
+
const keys = Object.keys(schema);
|
|
151
|
+
for (const key of keys) {
|
|
152
|
+
let def = schema[key];
|
|
153
|
+
const isOptional = ('optional' in def && def.optional) || ('types' in def &&
|
|
154
|
+
typeof def.types[0] === 'object' && 'optional' in def.types[0] && def.types[0].optional);
|
|
155
|
+
if (!(key in data)) {
|
|
156
|
+
if (isOptional) {
|
|
157
|
+
this.writeBit(0);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
throw new Error(`Missing key: ${key}`);
|
|
161
|
+
}
|
|
162
|
+
if (isOptional) {
|
|
163
|
+
this.writeBit(1);
|
|
164
|
+
}
|
|
165
|
+
const value = data[key];
|
|
166
|
+
if ('types' in def)
|
|
167
|
+
this.writeMultiType(value, def);
|
|
168
|
+
else
|
|
169
|
+
this.writeSingleType(value, def);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
write(data) {
|
|
173
|
+
this.writeObject(data, this.schema);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
type CreateSingleType<T extends string, V> = ({
|
|
2
|
+
optional: true;
|
|
3
|
+
default?: V;
|
|
4
|
+
} | {
|
|
5
|
+
optional?: false;
|
|
6
|
+
default?: never;
|
|
7
|
+
}) & ({
|
|
8
|
+
type: T;
|
|
9
|
+
});
|
|
10
|
+
type AllSingleTypes = CreateSingleType<'string', string> | CreateSingleType<`int-${8 | 16 | 32}`, number> | CreateSingleType<'bigint', bigint> | CreateSingleType<`float-${32 | 64}`, number> | CreateSingleType<'boolean', boolean>;
|
|
11
|
+
export type SingleType = AllSingleTypes | ({
|
|
12
|
+
type: 'null';
|
|
13
|
+
optional?: boolean;
|
|
14
|
+
default?: never;
|
|
15
|
+
}) | ({
|
|
16
|
+
type: 'object';
|
|
17
|
+
items: SchemaBase;
|
|
18
|
+
optional?: boolean;
|
|
19
|
+
}) | ({
|
|
20
|
+
type: 'array';
|
|
21
|
+
items: DataType;
|
|
22
|
+
optional?: boolean;
|
|
23
|
+
});
|
|
24
|
+
export type AllTypes = 'null' | 'string' | `int-${8 | 16 | 32}` | `float-${32 | 64}` | 'boolean' | 'bigint' | 'object' | 'array';
|
|
25
|
+
export type SimpleTypes = 'null' | 'string' | `int-${8 | 16 | 32}` | `float-${32 | 64}` | 'boolean' | 'bigint';
|
|
26
|
+
export type NonOptionalSingleType = ({
|
|
27
|
+
type: 'null';
|
|
28
|
+
} | {
|
|
29
|
+
type: 'string' | `int-${8 | 16 | 32}` | `float-${32 | 64}` | 'boolean' | 'bigint';
|
|
30
|
+
} | {
|
|
31
|
+
type: 'object';
|
|
32
|
+
items: SchemaBase;
|
|
33
|
+
} | {
|
|
34
|
+
type: 'array';
|
|
35
|
+
items: DataType;
|
|
36
|
+
}) & ({
|
|
37
|
+
optional?: never;
|
|
38
|
+
default?: never;
|
|
39
|
+
});
|
|
40
|
+
export type MultiType = {
|
|
41
|
+
types: [SingleType, ...Array<NonOptionalSingleType>] | [SimpleTypes, ...SimpleTypes[]];
|
|
42
|
+
};
|
|
43
|
+
export type DataType = SingleType | MultiType;
|
|
44
|
+
export type SchemaBase = Record<string, DataType>;
|
|
45
|
+
export type DataTypeValue = string | number | bigint | boolean | null | {
|
|
46
|
+
[key: string]: DataTypeValue;
|
|
47
|
+
} | DataTypeValue[];
|
|
48
|
+
export type DataValue = {
|
|
49
|
+
[key: string]: DataTypeValue;
|
|
50
|
+
};
|
|
51
|
+
export declare const createSchema: <S extends SchemaBase>(schema: S) => S;
|
|
52
|
+
export declare const encodeSchema: <S extends SchemaBase>(schema: S) => string;
|
|
53
|
+
export declare const decodeSchemaString: <S extends SchemaBase = SchemaBase>(schemaString: string) => [schema: S, metadata: string];
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
export const createSchema = (schema) => {
|
|
2
|
+
return schema;
|
|
3
|
+
};
|
|
4
|
+
// OPTIMIZATION 4: Improved Base62 with fast path
|
|
5
|
+
const BASE62_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
6
|
+
const encodeBase62 = (n) => {
|
|
7
|
+
if (n < 0)
|
|
8
|
+
throw new Error('Cannot encode negative numbers');
|
|
9
|
+
if (n < 62)
|
|
10
|
+
return BASE62_CHARS[n]; // Fast path for single digit
|
|
11
|
+
const digits = [];
|
|
12
|
+
while (n > 0) {
|
|
13
|
+
digits.push(BASE62_CHARS[n % 62]);
|
|
14
|
+
n = Math.floor(n / 62);
|
|
15
|
+
}
|
|
16
|
+
return digits.reverse().join('');
|
|
17
|
+
};
|
|
18
|
+
const decodeBase62 = (str) => {
|
|
19
|
+
let result = 0;
|
|
20
|
+
for (let i = 0; i < str.length; i++) {
|
|
21
|
+
const char = str[i];
|
|
22
|
+
const value = BASE62_CHARS.indexOf(char);
|
|
23
|
+
if (value === -1)
|
|
24
|
+
throw new Error(`Invalid base62 character: ${char}`);
|
|
25
|
+
result = result * 62 + value;
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
};
|
|
29
|
+
// OPTIMIZATION 2: Map-based lookups for O(1) complexity
|
|
30
|
+
const encodeSingleType = (type, key, keyMap, typeMap) => {
|
|
31
|
+
// Use Map for O(1) lookup instead of indexOf O(n)
|
|
32
|
+
let typeIndex = typeMap.get(type.type);
|
|
33
|
+
if (typeIndex === undefined) {
|
|
34
|
+
typeIndex = typeMap.size;
|
|
35
|
+
typeMap.set(type.type, typeIndex);
|
|
36
|
+
}
|
|
37
|
+
const typeIndexEncoded = encodeBase62(typeIndex);
|
|
38
|
+
// if (type.type === 'null') return typeIndexEncoded
|
|
39
|
+
if (type.type === 'object') {
|
|
40
|
+
const [keys, types] = encodeObjectSchema(type.items, keyMap, typeMap);
|
|
41
|
+
let typeStr = `{${types}}`;
|
|
42
|
+
if ('optional' in type && type.optional) {
|
|
43
|
+
typeStr += `?`;
|
|
44
|
+
}
|
|
45
|
+
return [`{${keys}}`, typeStr];
|
|
46
|
+
}
|
|
47
|
+
if (type.type === 'array') {
|
|
48
|
+
const [keys, types] = encodeDataType(type.items, key, keyMap, typeMap);
|
|
49
|
+
let typeStr = `[${types}]`;
|
|
50
|
+
if ('optional' in type && type.optional) {
|
|
51
|
+
typeStr += `?`;
|
|
52
|
+
}
|
|
53
|
+
return [`[${keys}]`, typeStr];
|
|
54
|
+
}
|
|
55
|
+
if (!('optional' in type))
|
|
56
|
+
return typeIndexEncoded;
|
|
57
|
+
return `${typeIndexEncoded}${type.optional ? `?${type.default !== undefined ? `${JSON.stringify(type.default)}` : ''}` : ''}`;
|
|
58
|
+
};
|
|
59
|
+
const encodeDataType = (datatype, key, keyMap, typeMap) => {
|
|
60
|
+
let keys = [];
|
|
61
|
+
let types = [];
|
|
62
|
+
if ("types" in datatype) {
|
|
63
|
+
const midTypes = [];
|
|
64
|
+
const midKeys = [];
|
|
65
|
+
let isFirst = true;
|
|
66
|
+
for (let valueType of datatype.types) {
|
|
67
|
+
if (typeof valueType === 'string')
|
|
68
|
+
valueType = { type: valueType };
|
|
69
|
+
if ("optional" in valueType && valueType.optional && !isFirst)
|
|
70
|
+
throw new Error(`Only first type can be optional for multi-type`);
|
|
71
|
+
isFirst = false;
|
|
72
|
+
const returnValue = encodeSingleType(valueType, key, keyMap, typeMap);
|
|
73
|
+
if (typeof returnValue === 'string') {
|
|
74
|
+
midTypes.push(returnValue);
|
|
75
|
+
midKeys.push(key);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const [vKey, vType] = returnValue;
|
|
79
|
+
midKeys.push(`${key}${vKey}`);
|
|
80
|
+
midTypes.push(`${vType}`);
|
|
81
|
+
}
|
|
82
|
+
types.push(midTypes.join('|'));
|
|
83
|
+
keys.push(midKeys.join('|'));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const returnValue = encodeSingleType(datatype, key, keyMap, typeMap);
|
|
87
|
+
if (typeof returnValue === 'string') {
|
|
88
|
+
keys.push(key);
|
|
89
|
+
types.push(encodeSingleType(datatype, key, keyMap, typeMap));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const [vKey, vType] = returnValue;
|
|
93
|
+
keys.push(`${key}${vKey}`);
|
|
94
|
+
types.push(`${vType}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return [keys, types];
|
|
98
|
+
};
|
|
99
|
+
// OPTIMIZATION 2 & 5: Map-based lookups + pre-sorted keys
|
|
100
|
+
const encodeObjectSchema = (schema, keyMap, typeMap) => {
|
|
101
|
+
let keys = [];
|
|
102
|
+
let types = [];
|
|
103
|
+
// OPTIMIZATION 5: Pre-sort keys once (avoid Object.entries tuple allocation)
|
|
104
|
+
const sortedKeys = Object.keys(schema).sort();
|
|
105
|
+
for (const key of sortedKeys) {
|
|
106
|
+
const value = schema[key];
|
|
107
|
+
// OPTIMIZATION 2: Use Map for O(1) lookup instead of indexOf O(n)
|
|
108
|
+
let keyIndex = keyMap.get(key);
|
|
109
|
+
if (keyIndex === undefined) {
|
|
110
|
+
keyIndex = keyMap.size;
|
|
111
|
+
keyMap.set(key, keyIndex);
|
|
112
|
+
}
|
|
113
|
+
const keyIndexEncoded = encodeBase62(keyIndex);
|
|
114
|
+
const [k, t] = encodeDataType(value, keyIndexEncoded, keyMap, typeMap);
|
|
115
|
+
keys.push(...k);
|
|
116
|
+
types.push(...t);
|
|
117
|
+
}
|
|
118
|
+
return [keys.join(','), types.join(',')];
|
|
119
|
+
};
|
|
120
|
+
export const encodeSchema = (schema) => {
|
|
121
|
+
const keyMap = new Map();
|
|
122
|
+
const typeMap = new Map();
|
|
123
|
+
const [keys, types] = encodeObjectSchema(schema, keyMap, typeMap);
|
|
124
|
+
const keyList = Array.from(keyMap.entries()).sort((a, b) => a[1] - b[1]).map(([k]) => k);
|
|
125
|
+
const typeList = Array.from(typeMap.entries()).sort((a, b) => a[1] - b[1]).map(([k]) => k);
|
|
126
|
+
return `${keyList.join(',')}\n${typeList.join(',')}\n${keys}\n${types}`;
|
|
127
|
+
};
|
|
128
|
+
const decodeObjectSchemaFromStrings = (keyList, typeList, keysEncoded, typesEncoded) => {
|
|
129
|
+
const keyTokens = splitTopLevel(keysEncoded, ',');
|
|
130
|
+
const typeTokens = splitTopLevel(typesEncoded, ',');
|
|
131
|
+
if (keyTokens.length !== typeTokens.length) {
|
|
132
|
+
throw new Error('Invalid schema string: keys/types length mismatch');
|
|
133
|
+
}
|
|
134
|
+
const out = {};
|
|
135
|
+
for (let i = 0; i < keyTokens.length; i++) {
|
|
136
|
+
const keyToken = keyTokens[i];
|
|
137
|
+
const typeToken = typeTokens[i];
|
|
138
|
+
const [keyIndex] = parseLeadingBase62(keyToken);
|
|
139
|
+
const keyName = keyList[keyIndex];
|
|
140
|
+
if (keyName === undefined)
|
|
141
|
+
throw new Error(`Invalid schema string: unknown key index ${keyIndex}`);
|
|
142
|
+
out[keyName] = decodeDataTypeFromTokens(keyList, typeList, keyToken, typeToken);
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
};
|
|
146
|
+
const splitTopLevel = (input, separator) => {
|
|
147
|
+
if (!input)
|
|
148
|
+
return [];
|
|
149
|
+
const parts = [];
|
|
150
|
+
let start = 0;
|
|
151
|
+
let braceDepth = 0;
|
|
152
|
+
let bracketDepth = 0;
|
|
153
|
+
for (let i = 0; i < input.length; i++) {
|
|
154
|
+
const ch = input[i];
|
|
155
|
+
if (ch === '{')
|
|
156
|
+
braceDepth++;
|
|
157
|
+
else if (ch === '}')
|
|
158
|
+
braceDepth--;
|
|
159
|
+
else if (ch === '[')
|
|
160
|
+
bracketDepth++;
|
|
161
|
+
else if (ch === ']')
|
|
162
|
+
bracketDepth--;
|
|
163
|
+
if (braceDepth === 0 && bracketDepth === 0 && ch === separator) {
|
|
164
|
+
parts.push(input.slice(start, i));
|
|
165
|
+
start = i + 1;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
parts.push(input.slice(start));
|
|
169
|
+
return parts;
|
|
170
|
+
};
|
|
171
|
+
const parseLeadingBase62 = (input) => {
|
|
172
|
+
let i = 0;
|
|
173
|
+
while (i < input.length && BASE62_CHARS.indexOf(input[i]) !== -1) {
|
|
174
|
+
i++;
|
|
175
|
+
}
|
|
176
|
+
if (i === 0)
|
|
177
|
+
throw new Error(`Invalid schema token: expected leading index, got: ${input}`);
|
|
178
|
+
const encoded = input.slice(0, i);
|
|
179
|
+
return [decodeBase62(encoded), i];
|
|
180
|
+
};
|
|
181
|
+
const stripWrapper = (input, open, close) => {
|
|
182
|
+
if (!input.startsWith(open) || !input.endsWith(close)) {
|
|
183
|
+
throw new Error(`Invalid schema token: expected ${open}...${close}, got: ${input}`);
|
|
184
|
+
}
|
|
185
|
+
return input.slice(1, -1);
|
|
186
|
+
};
|
|
187
|
+
const decodeDataTypeFromTokens = (keyList, typeList, keyToken, typeToken) => {
|
|
188
|
+
const typeParts = splitTopLevel(typeToken, '|');
|
|
189
|
+
if (typeParts.length > 1) {
|
|
190
|
+
const keyParts = splitTopLevel(keyToken, '|');
|
|
191
|
+
if (keyParts.length !== typeParts.length) {
|
|
192
|
+
throw new Error('Invalid schema string: multi-type keys/types length mismatch');
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
types: typeParts.map((t, idx) => decodeSingleTypeFromTokens(keyList, typeList, keyParts[idx], t))
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return decodeSingleTypeFromTokens(keyList, typeList, keyToken, typeToken);
|
|
199
|
+
};
|
|
200
|
+
const decodeSingleTypeFromTokens = (keyList, typeList, keyToken, typeToken) => {
|
|
201
|
+
if (typeToken.startsWith('{')) {
|
|
202
|
+
const braceEnd = typeToken.lastIndexOf('}');
|
|
203
|
+
if (braceEnd === -1)
|
|
204
|
+
throw new Error(`Invalid schema token: expected } in ${typeToken}`);
|
|
205
|
+
const [keyIndex, keyIndexLen] = parseLeadingBase62(keyToken);
|
|
206
|
+
const innerKeys = stripWrapper(keyToken.slice(keyIndexLen), '{', '}');
|
|
207
|
+
const innerTypes = typeToken.slice(1, braceEnd);
|
|
208
|
+
let out = {
|
|
209
|
+
type: 'object',
|
|
210
|
+
items: decodeObjectSchemaFromStrings(keyList, typeList, innerKeys, innerTypes)
|
|
211
|
+
};
|
|
212
|
+
const optionalPart = typeToken.slice(braceEnd + 1);
|
|
213
|
+
if (optionalPart.startsWith('?')) {
|
|
214
|
+
out.optional = true;
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
if (typeToken.startsWith('[')) {
|
|
219
|
+
const bracketEnd = typeToken.lastIndexOf(']');
|
|
220
|
+
if (bracketEnd === -1)
|
|
221
|
+
throw new Error(`Invalid schema token: expected ] in ${typeToken}`);
|
|
222
|
+
const [keyIndex, keyIndexLen] = parseLeadingBase62(keyToken);
|
|
223
|
+
const keySuffix = keyToken.slice(keyIndexLen);
|
|
224
|
+
const innerKeys = stripWrapper(keySuffix, '[', ']');
|
|
225
|
+
const innerTypes = typeToken.slice(1, bracketEnd);
|
|
226
|
+
const innerKeyTokens = splitTopLevel(innerKeys, ',');
|
|
227
|
+
const innerTypeTokens = splitTopLevel(innerTypes, ',');
|
|
228
|
+
if (innerKeyTokens.length !== 1 || innerTypeTokens.length !== 1) {
|
|
229
|
+
throw new Error('Invalid schema string: array item encoding malformed');
|
|
230
|
+
}
|
|
231
|
+
let out = {
|
|
232
|
+
type: 'array',
|
|
233
|
+
items: decodeDataTypeFromTokens(keyList, typeList, innerKeyTokens[0], innerTypeTokens[0])
|
|
234
|
+
};
|
|
235
|
+
const optionalPart = typeToken.slice(bracketEnd + 1);
|
|
236
|
+
if (optionalPart.startsWith('?')) {
|
|
237
|
+
out.optional = true;
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
const [typeIndexRaw, optionalRaw] = typeToken.split('?', 2);
|
|
242
|
+
const typeIndex = decodeBase62(typeIndexRaw);
|
|
243
|
+
const resolvedType = typeList[typeIndex];
|
|
244
|
+
if (!resolvedType)
|
|
245
|
+
throw new Error(`Invalid schema string: unknown type index ${typeIndex}`);
|
|
246
|
+
if (optionalRaw === undefined)
|
|
247
|
+
return { type: resolvedType };
|
|
248
|
+
const out = { type: resolvedType, optional: true };
|
|
249
|
+
if (optionalRaw.length)
|
|
250
|
+
out.default = JSON.parse(optionalRaw);
|
|
251
|
+
return out;
|
|
252
|
+
};
|
|
253
|
+
export const decodeSchemaString = (schemaString) => {
|
|
254
|
+
const lines = schemaString.split(/\r?\n/);
|
|
255
|
+
if (lines.length < 4 || lines.length > 5)
|
|
256
|
+
throw new Error('Invalid schema string: expected 4 or 5 lines');
|
|
257
|
+
let metadata = '';
|
|
258
|
+
if (lines.length === 5) {
|
|
259
|
+
metadata = lines.shift();
|
|
260
|
+
}
|
|
261
|
+
const keyList = lines[0].length ? lines[0].split(',') : [];
|
|
262
|
+
const typeList = lines[1].length ? lines[1].split(',') : [];
|
|
263
|
+
const keysEncoded = lines[2] ?? '';
|
|
264
|
+
const typesEncoded = lines[3] ?? '';
|
|
265
|
+
const schema = decodeObjectSchemaFromStrings(keyList, typeList, keysEncoded, typesEncoded);
|
|
266
|
+
return [schema, metadata];
|
|
267
|
+
};
|