const encoder = new TextEncoder(); export default class Schema { $$size = 0; specification; static viewGetMethods = { uint8: 'getUint8', int8: 'getInt8', uint16: 'getUint16', int16: 'getInt16', uint32: 'getUint32', int32: 'getInt32', float32: 'getFloat32', float64: 'getFloat64', int64: 'getBigInt64', uint64: 'getBigUint64', }; static viewSetMethods = { uint8: 'setUint8', int8: 'setInt8', uint16: 'setUint16', int16: 'setInt16', uint32: 'setUint32', int32: 'setInt32', float32: 'setFloat32', float64: 'setFloat64', int64: 'setBigInt64', uint64: 'setBigUint64', }; constructor(specification) { this.specification = this.constructor.normalize(specification); // Try to calculate static size. for (const i in this.specification) { const {type} = this.specification[i]; const size = this.constructor.sizeOfType(type); if (0 === size) { this.$$size = 0; break; } this.$$size += size; } } [Symbol.iterator]() { return Object.entries(this.specification).values(); } static defaultValueForType(type) { switch (type) { case 'uint8': case 'int8': case 'uint16': case 'int16': case 'uint32': case 'int32': case 'uint64': case 'int64': case 'float32': case 'float64': { return 0; } case 'string': { return ''; } } } has(key) { return key in this.specification; } static normalize(specification) { const normalized = Object.create(null); for (const i in specification) { normalized[i] = 'string' === typeof specification[i] ? {type: specification[i]} : specification[i]; if (!this.validateType(normalized[i].type)) { throw new TypeError(`unknown schema type: ${normalized[i].type}`); } normalized[i].defaultValue = normalized[i].defaultValue || this.defaultValueForType(normalized[i].type); } return normalized; } readSize(view, cursor) { let fullSize = 0; for (const i in this.specification) { const {type} = this.specification[i]; const size = this.constructor.sizeOfType(type); if (0 === size) { switch (type) { case 'string': { const length = view.getUint32(cursor, true); cursor += 4 + length; fullSize += 4; fullSize += length; break; } } } else { cursor += size; fullSize += size; } } return fullSize; } get size() { return this.$$size; } sizeOf(concrete) { let fullSize = 0; for (const i in this.specification) { const {type} = this.specification[i]; const size = this.constructor.sizeOfType(type); if (0 === size) { switch (type) { case 'string': fullSize += 4; fullSize += (encoder.encode(concrete[i])).length; break; } } else { fullSize += size; } } return fullSize; } static sizeOfType(type) { switch (type) { case 'uint8': case 'int8': { return 1; } case 'uint16': case 'int16': { return 2; } case 'uint32': case 'int32': case 'float32': { return 4; } case 'uint64': case 'int64': case 'float64': { return 8; } default: return 0; } } static validateType(type) { return [ 'uint8', 'int8', 'uint16', 'int16', 'uint32', 'int32', 'uint64', 'int64', 'float32', 'float64', 'string', ] .includes(type); } }