structurae
Advanced tools
Comparing version 2.1.0 to 2.2.0
@@ -7,9 +7,21 @@ # Changelog | ||
## [2.2.0] - 2020-02-26 | ||
### Added | ||
- Support boolean type in ObjectView. | ||
- Support type aliases in ObjectView. | ||
- Cache ArrayView & TypedArrayView classes to avoid duplications. | ||
- TypeView class to simplify creation of custom types for ObjectView. | ||
### Changed | ||
- (potentially breaking) Adding custom types to ObjectView is reworked. | ||
Custom types are now expected to be extensions of existing *View classes. | ||
A special TypeView class is added for types that extend number types. | ||
## [2.1.0] - 2020-02-11 | ||
### Added | ||
- BinaryProtocol class to simplify operating on tagged ObjectView | ||
- BinaryProtocol class to simplify operating on tagged ObjectView. | ||
## [2.0.1] - 2020-01-09 | ||
### Fixed | ||
- TypeScript type declarations for ObjectView | ||
- TypeScript type declarations for ObjectView. | ||
@@ -19,9 +31,9 @@ ## [2.0.0] - 2019-11-21 | ||
Deprecated classes and methods: | ||
- Remove RecordArray (consider using ObjectView instead) | ||
- Remove StringArrayView (use ArrayView instead) | ||
- Remove `toObject` methods of *View classes (use `toJSON` methods instead) | ||
- Remove RecordArray (consider using ObjectView instead). | ||
- Remove StringArrayView (use ArrayView instead). | ||
- Remove `toObject` methods of *View classes (use `toJSON` methods instead). | ||
### Changed | ||
- Rename `BitField.fields` to `BitField.schema`, simplify the schema definition | ||
- BitField no longer implicitly switches to using BigInts | ||
- Rename `BitField.fields` to `BitField.schema`, simplify the schema definition. | ||
- BitField no longer implicitly switches to using BigInts. | ||
- Add BigBitField that uses BigInts for bitfields longer than 31 bits. | ||
@@ -34,3 +46,3 @@ - BitFieldMixin automatically switches to BigBitField if the size of the bitfield exceeds 31 bits. | ||
### Added | ||
- Support default field values in ObjectView | ||
- Support default field values in ObjectView. | ||
@@ -43,11 +55,11 @@ ### Changed | ||
### Added | ||
- Add BitFieldMixin | ||
- Add BitFieldMixin. | ||
### Fixed | ||
- Avoid BigInts in RecordArray if not supported | ||
- Avoid BigInts in RecordArray if not supported. | ||
## [1.7.4] - 2019-09-21 | ||
### Added | ||
- Add ObjectViewMixin and expose ArrayView | ||
- Add ObjectView#getView | ||
- Add ObjectViewMixin and expose ArrayView. | ||
- Add ObjectView#getView. | ||
@@ -54,0 +66,0 @@ ## [1.7.3] - 2019-09-13 |
@@ -200,6 +200,24 @@ // Type definitions for structurae | ||
type ViewType = typeof ArrayView | typeof ObjectView | typeof TypedArrayView | typeof StringView; | ||
type ViewType = typeof ArrayView | typeof ObjectView | typeof TypedArrayView | typeof StringView | typeof TypeView; | ||
type View = ObjectView | ArrayView | TypedArrayView | StringView; | ||
type View = ObjectView | ArrayView | TypedArrayView | StringView | TypeView; | ||
declare class TypeView extends DataView { | ||
static isPrimitive: true; | ||
private static offset: number; | ||
private static littleEndian: true; | ||
private static objectLength: number; | ||
get(): number; | ||
set(value: number): this; | ||
toJSON(): number; | ||
static getLength(): number; | ||
static from(value: number, view?: TypeView): TypeView; | ||
static of(): TypeView; | ||
static get(position: number, view: View): number; | ||
static set(position: number, value: number, view: View): void; | ||
} | ||
export declare function TypeViewMixin(type: PrimitiveFieldType, littleEndian?: boolean): typeof TypeView; | ||
export declare class ArrayView extends DataView { | ||
@@ -236,5 +254,2 @@ size: number; | ||
View?: ViewType; | ||
getter?: string; | ||
setter?: string; | ||
itemLength?: number; | ||
default?: any; | ||
@@ -255,2 +270,3 @@ } | ||
static isInitialized: boolean; | ||
static isPrimitive: false; | ||
private static fields: string[]; | ||
@@ -261,9 +277,9 @@ private static objectLength: number; | ||
get(field: string): number | View; | ||
private getObject(position: number, field: ObjectViewField): object; | ||
private getTypedArray(position: number, field: ObjectViewField): ArrayLike<number>; | ||
private getPrimitive(position: number, View: ViewType): object; | ||
private getObject(position: number, View: ViewType, length: number): object; | ||
getValue(field: string): any; | ||
getView(field: string): View; | ||
set(field: string, value: any): this; | ||
private setObject(position: number, value: object, field: ObjectViewField): void; | ||
private setTypedArray(position: number, value: ArrayLike<number>, field: ObjectViewField): void; | ||
private setPrimitive(position: number, value: number, View: ViewType): void; | ||
private setObject(position: number, value: object, View: ViewType, length: number): void; | ||
setView(field: string, value: View): this; | ||
@@ -274,3 +290,2 @@ toJSON(): object; | ||
static initialize(): void; | ||
private static getFieldKind(field: ObjectViewField): string; | ||
} | ||
@@ -306,6 +321,4 @@ | ||
size: number; | ||
static typeGetter: string; | ||
static typeSetter: string; | ||
static offset: number; | ||
static littleEndian: boolean; | ||
static View: TypeView; | ||
static itemLength: number; | ||
@@ -312,0 +325,0 @@ get(index: number): number; |
@@ -16,8 +16,10 @@ const BitField = require('./lib/bit-field'); | ||
const WeightedAdjacencyMatrixMixin = require('./lib/weighted-adjacency-matrix'); | ||
const { ArrayView, ArrayViewMixin } = require('./lib/array-view'); | ||
const ArrayView = require('./lib/array-view'); | ||
const { ArrayViewMixin, TypedArrayViewMixin } = require('./lib/array-view-mixin'); | ||
const CollectionView = require('./lib/collection-view'); | ||
const { ObjectView, ObjectViewMixin } = require('./lib/object-view'); | ||
const StringView = require('./lib/string-view'); | ||
const TypedArrayViewMixin = require('./lib/typed-array-view'); | ||
const BinaryProtocol = require('./lib/binary-protocol'); | ||
const TypeViewMixin = require('./lib/type-view'); | ||
const BooleanView = require('./lib/boolean-view'); | ||
@@ -63,2 +65,4 @@ /** | ||
BinaryProtocol, | ||
TypeViewMixin, | ||
BooleanView, | ||
}; |
@@ -88,3 +88,3 @@ /** | ||
for (let i = 0; i < size; i++) { | ||
json[i] = this.get(i).toJSON(); | ||
json[i] = this.getValue(i); | ||
} | ||
@@ -146,23 +146,2 @@ return json; | ||
/** | ||
* Creates an ArrayView class for a given ObjectView class. | ||
* | ||
* @param {Class<ObjectView>|Class<StringView>} ObjectViewClass | ||
* @param {number} [itemLength] | ||
* @returns {Class<ArrayView>} | ||
*/ | ||
function ArrayViewMixin(ObjectViewClass, itemLength) { | ||
if (ObjectViewClass.initialize && !ObjectViewClass.isInitialized) { | ||
ObjectViewClass.initialize(); | ||
} | ||
class Base extends ArrayView {} | ||
Base.View = ObjectViewClass; | ||
Base.itemLength = ObjectViewClass.objectLength || itemLength; | ||
return Base; | ||
} | ||
module.exports = { | ||
ArrayView, | ||
ArrayViewMixin, | ||
}; | ||
module.exports = ArrayView; |
const StringView = require('./string-view'); | ||
const { ArrayViewMixin } = require('./array-view'); | ||
const TypedArrayViewMixin = require('./typed-array-view'); | ||
const { typeOffsets, typeGetters, typeSetters } = require('./utilities'); | ||
const { ArrayViewMixin, TypedArrayViewMixin } = require('./array-view-mixin'); | ||
const TypeViewMixin = require('./type-view'); | ||
const BooleanView = require('./boolean-view'); | ||
/** | ||
* @typedef {Class<ArrayView>|Class<ObjectView>|Class<TypedArrayView>|Class<StringView>} ViewType | ||
* @typedef {Class<ArrayView>|Class<ObjectView>|Class<TypedArrayView> | ||
* |Class<StringView>|Class<TypeView>} ViewType | ||
*/ | ||
@@ -31,5 +32,2 @@ | ||
* @property {ViewType} [View] | ||
* @property {string} [getter] | ||
* @property {string} [setter] | ||
* @property {number} [itemLength] | ||
* @property {*} [default] | ||
@@ -59,7 +57,5 @@ */ | ||
get(field) { | ||
const { | ||
getter, start, kind, littleEndian, View, length, | ||
} = this.constructor.schema[field]; | ||
if (kind === 'number') return this[getter](start, littleEndian); | ||
return new View(this.buffer, this.byteOffset + start, length); | ||
const { start, View, length } = this.constructor.schema[field]; | ||
return View.isPrimitive ? this.getPrimitive(start, View) | ||
: new View(this.buffer, this.byteOffset + start, length); | ||
} | ||
@@ -70,9 +66,7 @@ | ||
* @param {number} position | ||
* @param {ObjectViewField} field | ||
* @returns {Object} | ||
* @param {ViewType} View | ||
* @returns {number} | ||
*/ | ||
getObject(position, field) { | ||
const { View, length } = field; | ||
const view = new View(this.buffer, this.byteOffset + position, length); | ||
return view.toJSON(); | ||
getPrimitive(position, View) { | ||
return View.get(position, this); | ||
} | ||
@@ -83,13 +77,8 @@ | ||
* @param {number} position | ||
* @param {ObjectViewField} field | ||
* @returns {Array<number>} | ||
* @param {ViewType} View | ||
* @param {number} length | ||
* @returns {Object} | ||
*/ | ||
getTypedArray(position, field) { | ||
const { View, size } = field; | ||
const { typeGetter, offset, littleEndian } = View; | ||
const result = new Array(size); | ||
for (let i = 0; i < size; i++) { | ||
result[i] = this[typeGetter](position + (i << offset), littleEndian); | ||
} | ||
return result; | ||
getObject(position, View, length) { | ||
return new View(this.buffer, this.byteOffset + position, length).toJSON(); | ||
} | ||
@@ -104,6 +93,4 @@ | ||
getValue(field) { | ||
const options = this.constructor.schema[field]; | ||
const { start, getter, kind } = options; | ||
const arg = kind === 'number' ? options.littleEndian : options; | ||
return this[getter](start, arg); | ||
const { start, View, length } = this.constructor.schema[field]; | ||
return View.isPrimitive ? this.getPrimitive(start, View) : this.getObject(start, View, length); | ||
} | ||
@@ -130,8 +117,8 @@ | ||
set(field, value) { | ||
const options = this.constructor.schema[field]; | ||
const { | ||
kind, start, setter, littleEndian, | ||
} = options; | ||
const arg = kind === 'number' ? littleEndian : options; | ||
this[setter](start, value, arg); | ||
const { start, View, length } = this.constructor.schema[field]; | ||
if (View.isPrimitive) { | ||
this.setPrimitive(start, value, View); | ||
} else { | ||
this.setObject(start, value, View, length); | ||
} | ||
return this; | ||
@@ -143,10 +130,8 @@ } | ||
* @param {number} position | ||
* @param {Object} value | ||
* @param {ObjectViewField} field | ||
* @param {number} value | ||
* @param {ViewType} View | ||
* @returns {void} | ||
*/ | ||
setObject(position, value, field) { | ||
const { View, length } = field; | ||
const view = new View(this.buffer, this.byteOffset + position, length); | ||
View.from(value, view); | ||
setPrimitive(position, value, View) { | ||
View.set(position, value, this); | ||
} | ||
@@ -157,15 +142,10 @@ | ||
* @param {number} position | ||
* @param {ArrayLike<number>} value | ||
* @param {ObjectViewField} field | ||
* @param {Object} value | ||
* @param {ViewType} View | ||
* @param {number} length | ||
* @returns {void} | ||
*/ | ||
setTypedArray(position, value, field) { | ||
const { View, size, length } = field; | ||
const { typeSetter, offset, littleEndian } = View; | ||
new Uint8Array(this.buffer, this.byteOffset + position, length) | ||
.fill(0); | ||
const max = (size < value.length ? size : value.length); | ||
for (let i = 0; i < max; i++) { | ||
this[typeSetter](position + (i << offset), value[i], littleEndian); | ||
} | ||
setObject(position, value, View, length) { | ||
const view = new View(this.buffer, this.byteOffset + position, length); | ||
View.from(value, view); | ||
} | ||
@@ -269,9 +249,6 @@ | ||
field.start = lastOffset; | ||
const kind = this.getFieldKind(field); | ||
if (Reflect.has(this.types, kind)) { | ||
field.kind = kind; | ||
this.types[kind](field); | ||
} else { | ||
throw TypeError(`Type "${field.type}" is not a valid type.`); | ||
} | ||
const type = typeof field.type !== 'string' ? 'object' : field.type; | ||
if (!Reflect.has(this.types, type)) throw TypeError(`Type "${field.type}" is not a valid type.`); | ||
const definition = typeof this.types[type] === 'string' ? this.types[this.types[type]] : this.types[type]; | ||
if (definition) definition(field); | ||
lastOffset += field.length; | ||
@@ -284,32 +261,2 @@ } | ||
} | ||
/** | ||
* @private | ||
* @param {ObjectViewField} field | ||
* @returns {string} | ||
*/ | ||
static getFieldKind(field) { | ||
const { type, size } = field; | ||
switch (type) { | ||
case 'int8': | ||
case 'uint8': | ||
case 'int16': | ||
case 'uint16': | ||
case 'int32': | ||
case 'uint32': | ||
case 'float32': | ||
case 'float64': | ||
case 'bigint64': | ||
case 'biguint64': | ||
if (size) return 'typedarray'; | ||
return 'number'; | ||
case 'string': | ||
if (size) return 'array'; | ||
return 'string'; | ||
default: | ||
if (typeof type === 'string') return type; | ||
if (size) return 'array'; | ||
return 'object'; | ||
} | ||
} | ||
} | ||
@@ -321,2 +268,13 @@ | ||
ObjectView.types = { | ||
int8: 'number', | ||
uint8: 'number', | ||
int16: 'number', | ||
uint16: 'number', | ||
int32: 'number', | ||
uint32: 'number', | ||
float32: 'number', | ||
float64: 'number', | ||
bigint64: 'number', | ||
biguint64: 'number', | ||
/** | ||
@@ -326,9 +284,8 @@ * @param {ObjectViewField} field | ||
*/ | ||
number(field) { | ||
const { type, littleEndian } = field; | ||
field.View = TypedArrayViewMixin(type, littleEndian); | ||
field.length = 1 << typeOffsets[type]; | ||
field.getter = typeGetters[type]; | ||
field.setter = typeSetters[type]; | ||
boolean(field) { | ||
const { size } = field; | ||
field.View = size ? ArrayViewMixin(BooleanView) : BooleanView; | ||
field.length = field.View.getLength(size || 1); | ||
}, | ||
/** | ||
@@ -338,9 +295,8 @@ * @param {ObjectViewField} field | ||
*/ | ||
typedarray(field) { | ||
number(field) { | ||
const { type, littleEndian, size } = field; | ||
field.View = TypedArrayViewMixin(type, littleEndian); | ||
field.length = field.View.getLength(size); | ||
field.getter = 'getTypedArray'; | ||
field.setter = 'setTypedArray'; | ||
field.View = size ? TypedArrayViewMixin(type, littleEndian) : TypeViewMixin(type, littleEndian); | ||
field.length = field.View.getLength(size || 1); | ||
}, | ||
/** | ||
@@ -351,6 +307,7 @@ * @param {ObjectViewField} field | ||
string(field) { | ||
field.View = StringView; | ||
field.getter = 'getObject'; | ||
field.setter = 'setObject'; | ||
const { size, length } = field; | ||
field.View = size ? ArrayViewMixin(StringView, length) : StringView; | ||
field.length = size ? field.View.getLength(size) : field.length; | ||
}, | ||
/** | ||
@@ -361,19 +318,5 @@ * @param {ObjectViewField} field | ||
object(field) { | ||
const { type } = field; | ||
field.View = type; | ||
field.length = type.getLength(); | ||
field.getter = 'getObject'; | ||
field.setter = 'setObject'; | ||
}, | ||
/** | ||
* @param {ObjectViewField} field | ||
* @returns {void} | ||
*/ | ||
array(field) { | ||
const { type, size, length } = field; | ||
field.View = type === 'string' ? ArrayViewMixin(StringView, length) : ArrayViewMixin(type); | ||
const { type, size } = field; | ||
field.View = size ? ArrayViewMixin(type) : type; | ||
field.length = field.View.getLength(size); | ||
field.getter = 'getObject'; | ||
field.setter = 'setObject'; | ||
field.itemLength = field.View.itemLength; | ||
}, | ||
@@ -390,2 +333,5 @@ }; | ||
/** @type {boolean} */ | ||
ObjectView.isPrimitive = false; | ||
/** | ||
@@ -392,0 +338,0 @@ * @private |
@@ -1,3 +0,1 @@ | ||
const { typeGetters, typeSetters, typeOffsets } = require('./utilities'); | ||
/** | ||
@@ -16,4 +14,4 @@ * A DataView based TypedArray that supports endianness and can be set at any offset. | ||
get(index) { | ||
const { typeGetter, offset, littleEndian: le } = this.constructor; | ||
return this[typeGetter](index << offset, le); | ||
const { View } = this.constructor; | ||
return View.get(index << View.offset, this); | ||
} | ||
@@ -29,4 +27,4 @@ | ||
set(index, value) { | ||
const { typeSetter, offset, littleEndian: le } = this.constructor; | ||
this[typeSetter](index << offset, value, le); | ||
const { View } = this.constructor; | ||
View.set(index << View.offset, value, this); | ||
return this; | ||
@@ -41,3 +39,3 @@ } | ||
get size() { | ||
return this.byteLength >> this.constructor.offset; | ||
return this.byteLength >> this.constructor.View.offset; | ||
} | ||
@@ -74,3 +72,3 @@ | ||
static getLength(size) { | ||
return size << this.offset; | ||
return size << this.View.offset; | ||
} | ||
@@ -86,8 +84,12 @@ | ||
static from(value, array) { | ||
const dataArray = array || this.of(value.length); | ||
const { size } = dataArray; | ||
const view = array || this.of(value.length); | ||
if (array) { | ||
new Uint8Array(array.buffer, array.byteOffset, array.byteLength) | ||
.fill(0); | ||
} | ||
const { size } = view; | ||
for (let i = 0; i < size; i++) { | ||
dataArray.set(i, value[i]); | ||
view.set(i, value[i]); | ||
} | ||
return dataArray; | ||
return view; | ||
} | ||
@@ -108,48 +110,11 @@ | ||
/** | ||
* @type {string} | ||
* @type {Class<TypeView>} | ||
*/ | ||
TypedArrayView.typeGetter = ''; | ||
TypedArrayView.View = undefined; | ||
/** | ||
* @type {string} | ||
*/ | ||
TypedArrayView.typeSetter = ''; | ||
/** | ||
* @type {number} | ||
*/ | ||
TypedArrayView.offset = 0; | ||
TypedArrayView.itemLength = 0; | ||
/** | ||
* @type {boolean} | ||
*/ | ||
TypedArrayView.littleEndian = false; | ||
const TypedArrayViews = Object.keys(typeGetters).reduce((result, type) => { | ||
class BE extends TypedArrayView {} | ||
BE.typeGetter = typeGetters[type]; | ||
BE.typeSetter = typeSetters[type]; | ||
BE.offset = typeOffsets[type]; | ||
BE.littleEndian = false; | ||
class LE extends TypedArrayView {} | ||
LE.typeGetter = typeGetters[type]; | ||
LE.typeSetter = typeSetters[type]; | ||
LE.offset = typeOffsets[type]; | ||
LE.littleEndian = true; | ||
result[0][type] = BE; | ||
result[1][type] = LE; | ||
return result; | ||
}, { 0: {}, 1: {} }); | ||
/** | ||
* @param {string} type | ||
* @param {boolean} [littleEndian] | ||
* @returns {Class<Base>} | ||
*/ | ||
function TypedArrayViewMixin(type, littleEndian) { | ||
return TypedArrayViews[+!!littleEndian][type]; | ||
} | ||
module.exports = TypedArrayViewMixin; | ||
module.exports = TypedArrayView; |
{ | ||
"name": "structurae", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"description": "Data structures for performance-sensitive modern JavaScript applications.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -59,3 +59,3 @@ # Structurae | ||
The fields are defined in ObjectView.schema and can be of any primitive type supported by DataView, | ||
their arrays, strings, or other objects and arrays of objects. The data is laid out sequentially with fixed sizes, hence, | ||
their arrays, booleans, strings, or other objects and arrays of objects. The data is laid out sequentially with fixed sizes, hence, | ||
variable length arrays and optional fields are not supported (for those check out [CollectionView](https://github.com/zandaqo/structurae#CollectionView)). | ||
@@ -136,33 +136,33 @@ | ||
You can add your own field types to ObjectView, for example an ObjectView that supports booleans: | ||
You can add your own field types to ObjectView, for example an ObjectView that supports Date: | ||
```javascript | ||
class BooleanView extends ObjectView { | ||
getBoolean(position) { | ||
return !!this.getUint8(position); | ||
const { TypeViewMixin } = require('structurae'); | ||
class DateView extends TypeViewMixin('float64', true) { | ||
static get(position, view) { | ||
return new Date(super.get(position, view)); | ||
} | ||
setBoolean(position, value) { | ||
this.setUint8(position, value ? 1 : 0); | ||
static set(position, value, view) { | ||
super.set(position, +value, view); | ||
} | ||
} | ||
BooleanView.schema = { | ||
a: { type: 'boolean' }, | ||
class View extends ObjectView {} | ||
View.schema = { | ||
a: { type: 'date' }, | ||
}; | ||
BooleanView.types = { | ||
View.types = { | ||
...ObjectView.types, | ||
boolean(field) { | ||
field.View = DataView; | ||
field.length = 1; | ||
field.getter = 'getBoolean'; | ||
field.setter = 'setBoolean'; | ||
date(field) { | ||
field.View = DateView; | ||
field.length = 8; | ||
}, | ||
}; | ||
BooleanView.initialize(); | ||
View.initialize(); | ||
const bool = BooleanView.from({ a: true }); | ||
bool.getValue('a') | ||
//=> true | ||
bool.set('a', false); | ||
bool.toJSON(); | ||
//=> { a: false } | ||
const date = View.from({ a: new Date(0) }); | ||
date.getValue('a') | ||
//=> Thu Jan 01 1970 00:00:00 GMT+0000 | ||
date.set('a', new Date(1e8)); | ||
date.toJSON(); | ||
//=> { a: Fri Jan 02 1970 03:46:40 GMT+0000 } | ||
``` | ||
@@ -348,3 +348,3 @@ | ||
We can of course define ObjectViews separately, however we will have to specify that tag field by ourselves in that case. | ||
We can define ObjectViews separately, however, we will have to specify the tag field by ourselves in that case. | ||
```javascript | ||
@@ -351,0 +351,0 @@ const View = ObjectViewMixin({ |
205263
33
5626