structurae
Advanced tools
Comparing version 1.7.3 to 1.7.4
@@ -7,11 +7,16 @@ # Changelog | ||
## [1.7.4] - 2019-09-21 | ||
### Added | ||
- Add ObjectViewMixin and expose ArrayView | ||
- Add ObjectView#getView | ||
## [1.7.3] - 2019-09-13 | ||
## Fixed | ||
### Fixed | ||
- Handle non-string values in StringView.from | ||
## [1.7.2] - 2019-09-13 | ||
## Added | ||
### Added | ||
- Add StringView.from that uses TextEncoder#encodeInto | ||
## Changed | ||
### Changed | ||
- Support strings in ArrayView replacing StringArrayView | ||
@@ -18,0 +23,0 @@ |
@@ -215,3 +215,3 @@ // Type definitions for structurae | ||
declare class ArrayView extends DataView { | ||
export declare class ArrayView extends DataView { | ||
size: number; | ||
@@ -267,2 +267,3 @@ static itemLength: number; | ||
getValue(field: string): any; | ||
getView(field: string): View; | ||
set(field: string, value: any): this; | ||
@@ -280,2 +281,4 @@ private setObject(position: number, value: object, field: ObjectViewField): void; | ||
export declare function ObjectViewMixin(schema: ObjectViewSchema, ObjectViewClass?: typeof ObjectView): typeof ObjectView; | ||
export declare class StringView extends Uint8Array { | ||
@@ -282,0 +285,0 @@ size: number; |
@@ -15,5 +15,5 @@ const BitField = require('./lib/bit-field'); | ||
const WeightedAdjacencyMatrixMixin = require('./lib/weighted-adjacency-matrix'); | ||
const ArrayViewMixin = require('./lib/array-view'); | ||
const { ArrayView, ArrayViewMixin } = require('./lib/array-view'); | ||
const CollectionView = require('./lib/collection-view'); | ||
const ObjectView = require('./lib/object-view'); | ||
const { ObjectView, ObjectViewMixin } = require('./lib/object-view'); | ||
const StringView = require('./lib/string-view'); | ||
@@ -53,5 +53,7 @@ const StringArrayView = require('./lib/string-array-view'); | ||
WeightedAdjacencyMatrixMixin, | ||
ArrayView, | ||
ArrayViewMixin, | ||
CollectionView, | ||
ObjectView, | ||
ObjectViewMixin, | ||
StringView, | ||
@@ -58,0 +60,0 @@ StringArrayView, |
@@ -156,2 +156,4 @@ /** | ||
/** | ||
* Creates an ArrayView class for a given ObjectView class. | ||
* | ||
* @param {Class<ObjectView>|Class<StringView>} ObjectViewClass | ||
@@ -172,2 +174,5 @@ * @param {number} [itemLength] | ||
module.exports = ArrayViewMixin; | ||
module.exports = { | ||
ArrayView, | ||
ArrayViewMixin, | ||
}; |
const StringView = require('./string-view'); | ||
const ArrayViewMixin = require('./array-view'); | ||
const { ArrayViewMixin } = require('./array-view'); | ||
const TypedArrayViewMixin = require('./typed-array-view'); | ||
@@ -43,3 +43,3 @@ const { typeOffsets, typeGetters, typeSetters } = require('./utilities'); | ||
/** | ||
* Returns the value or view of a given field. | ||
* Returns a number for primitive fields or a view for all other fields. | ||
* | ||
@@ -92,5 +92,5 @@ * @param {string} field the name of the field | ||
getValue(field) { | ||
const fieldOptions = this.constructor.schema[field]; | ||
const { start, getter, kind } = fieldOptions; | ||
const arg = kind === 'number' ? fieldOptions.littleEndian : fieldOptions; | ||
const options = this.constructor.schema[field]; | ||
const { start, getter, kind } = options; | ||
const arg = kind === 'number' ? options.littleEndian : options; | ||
return this[getter](start, arg); | ||
@@ -100,2 +100,13 @@ } | ||
/** | ||
* Returns a view of a given field. | ||
* | ||
* @param {string} field the name of the field | ||
* @returns {*} view of the field | ||
*/ | ||
getView(field) { | ||
const { View, start, length } = this.constructor.schema[field]; | ||
return new View(this.buffer, this.byteOffset + start, length); | ||
} | ||
/** | ||
* Sets a JavaScript value to a field. | ||
@@ -108,7 +119,7 @@ * | ||
set(field, value) { | ||
const fieldOptions = this.constructor.schema[field]; | ||
const options = this.constructor.schema[field]; | ||
const { | ||
kind, start, setter, littleEndian, | ||
} = fieldOptions; | ||
const arg = kind === 'number' ? littleEndian : fieldOptions; | ||
} = options; | ||
const arg = kind === 'number' ? littleEndian : options; | ||
this[setter](start, value, arg); | ||
@@ -287,3 +298,4 @@ return this; | ||
number(field) { | ||
const { type } = field; | ||
const { type, littleEndian } = field; | ||
field.View = TypedArrayViewMixin(type, littleEndian); | ||
field.length = 1 << typeOffsets[type]; | ||
@@ -357,2 +369,19 @@ field.getter = typeGetters[type]; | ||
module.exports = ObjectView; | ||
/** | ||
* Creates an ObjectView class with a given schema. | ||
* | ||
* @param {object} schema the schema to use for the class | ||
* @param {Class<ObjectView>} [ObjectViewClass] an optional ObjectView class to extend | ||
* @returns {Class<ObjectView>} | ||
*/ | ||
function ObjectViewMixin(schema, ObjectViewClass = ObjectView) { | ||
class Base extends ObjectViewClass {} | ||
Base.schema = schema; | ||
Base.initialize(); | ||
return Base; | ||
} | ||
module.exports = { | ||
ObjectView, | ||
ObjectViewMixin, | ||
}; |
@@ -132,2 +132,20 @@ const { typeGetters, typeSetters, typeOffsets } = require('./utilities'); | ||
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: {} }); | ||
/** | ||
@@ -139,10 +157,5 @@ * @param {string} type | ||
function TypedArrayViewMixin(type, littleEndian) { | ||
class Base extends TypedArrayView {} | ||
Base.typeGetter = typeGetters[type]; | ||
Base.typeSetter = typeSetters[type]; | ||
Base.offset = typeOffsets[type]; | ||
Base.littleEndian = !!littleEndian; | ||
return Base; | ||
return TypedArrayViews[+!!littleEndian][type]; | ||
} | ||
module.exports = TypedArrayViewMixin; |
{ | ||
"name": "structurae", | ||
"version": "1.7.3", | ||
"version": "1.7.4", | ||
"description": "Data structures for performance-sensitive modern JavaScript applications.", | ||
@@ -50,4 +50,4 @@ "main": "index.js", | ||
"benchmark": "^2.1.4", | ||
"codecov": "^3.5.0", | ||
"eslint": "^6.3.0", | ||
"codecov": "^3.6.1", | ||
"eslint": "^6.4.0", | ||
"eslint-config-airbnb-base": "^14.0.0", | ||
@@ -57,3 +57,3 @@ "eslint-plugin-import": "^2.18.2", | ||
"jsdoc-to-markdown": "^5.0.1", | ||
"json-schema-faker": "^0.5.0-rc17" | ||
"json-schema-faker": "^0.5.0-rc19" | ||
}, | ||
@@ -60,0 +60,0 @@ "jest": { |
204
README.md
@@ -10,4 +10,5 @@ # Structurae | ||
- [Binary Structures](https://github.com/zandaqo/structurae#binary-structures): | ||
- [ArrayView](https://github.com/zandaqo/structurae#ArrayView) - an array of C-like structs, ObjectViews, implemented with DataView | ||
- [ObjectView](https://github.com/zandaqo/structurae#ObjectView) - extends DataView to implement C-like struct. | ||
- [ArrayView](https://github.com/zandaqo/structurae#ArrayView) - an array of C-like structs, ObjectViews. | ||
- [CollectionView](https://github.com/zandaqo/structurae#CollectionView) - an array of ObjectViews and ArrayViews of different types that support optional members. | ||
- [StringView](https://github.com/zandaqo/structurae#StringView) - extends Uint8Array to handle C-like representation of UTF-8 encoded strings. | ||
@@ -51,15 +52,103 @@ - [TypedArrayView](https://github.com/zandaqo/structurae#TypedArrayView) - a DataView based TypedArray that supports endianness and can be set at any offset. | ||
support using ArrayBuffers for strings, objects, and arrays of objects defined with schema akin to C-like structs. | ||
Useful on their own, when combined, these classes form the basis for a simple, zero-copy binary protocol that is smaller and faster than | ||
other binary formats used in JavaScript, such as BSON or MessagePack. | ||
Useful on their own, when combined, these classes form the basis for a simple binary protocol that is smaller and faster than | ||
schema-less binary formats (e.g. BSON, MessagePac) and supports zero-copy operations. Unlike other schema-based formats | ||
(e.g. Flatbuffers), these interfaces are native to JavaScript, hence, supported in all modern browsers and Node.js, | ||
and do not require compilation. | ||
#### ObjectView | ||
Extends DataView to store a JavaScript object in ArrayBuffer akin to C-like struct. | ||
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, | ||
variable length arrays and optional fields are not supported (for those check out [CollectionView](https://github.com/zandaqo/structurae#CollectionView)). | ||
```javascript | ||
const { ObjectViewMixin } = require('structurae'); | ||
const House = ObjectViewMixin({ | ||
size: { type: 'uint32' }, // a primitive type | ||
}); | ||
const Pet = ObjectViewMixin({ | ||
type: { type: 'string', length: 10 }, // // string with max length of 10 bytes | ||
}); | ||
const Person = ObjectViewMixin({ | ||
name: { type: 'string', length: 10 }, | ||
fullName: { type: 'string', size: 2, length: 10 }, // an array of 2 strings 10 bytes long each | ||
scores: { type: 'uint32', size: 10 }, // a an array of 10 numbers | ||
house: { type: House }, // nested object view | ||
pets: { type: Pet, size: 3 }, // an array of 3 pet objects | ||
}); | ||
const person = Person.from({ | ||
name: 'Zaphod', | ||
fullName: ['Zaphod', 'Beeblebrox'], | ||
scores: [1, 2, 3], | ||
house: { | ||
size: 1, | ||
}, | ||
pets: [ | ||
{ type: 'dog' }, { type: 'cat' } | ||
], | ||
}); | ||
person.byteLength | ||
//=> 64 | ||
person.get('scores').get(0) | ||
//=> 1 | ||
person.get('name') | ||
//=> StringView [10] | ||
person.getValue('name'); | ||
//=> Zaphod | ||
person.getValue('scores') | ||
//=> [1, 2, 3, 0, 0, 0, 0, 0, 0, 0,] | ||
person.set('house', { size: 5 }); | ||
person.getValue('house'); | ||
//=> { size: 5 } | ||
person.toJSON() | ||
//=> { name: 'Zaphod', fullName: ['Zaphod', 'Beeblebrox'], scores: [1, 2, 3, 0, 0, 0, 0, 0, 0, 0,], | ||
// house: { size: 5 }, pets: [{ type: 'dog' }, { type: 'cat' }, { type: '' }] } | ||
``` | ||
You can add your own field types to ObjectView, for example an ObjectView that supports booleans: | ||
```javascript | ||
class BooleanView extends ObjectView { | ||
getBoolean(position) { | ||
return !!this.getUint8(position); | ||
} | ||
setBoolean(position, value) { | ||
this.setUint8(position, value ? 1 : 0); | ||
} | ||
} | ||
BooleanView.schema = { | ||
a: { type: 'boolean' }, | ||
}; | ||
BooleanView.types = { | ||
...ObjectView.types, | ||
boolean(field) { | ||
field.View = DataView; | ||
field.length = 1; | ||
field.getter = 'getBoolean'; | ||
field.setter = 'setBoolean'; | ||
}, | ||
}; | ||
BooleanView.initialize(); | ||
const bool = BooleanView.from({ a: true }); | ||
bool.getValue('a') | ||
//=> true | ||
bool.set('a', false); | ||
bool.toJSON(); | ||
//=> { a: false } | ||
``` | ||
#### ArrayView | ||
DataView based array of ObjectViews (aka C-like structs): | ||
DataView based array of objects or more precisely ObjectViews: | ||
```javascript | ||
const { ObjectView, ArrayViewMixin } = require('structurae'); | ||
const { ObjectViewMixin, ArrayViewMixin } = require('structurae'); | ||
class Person extends ObjectView {} | ||
Person.schema = { | ||
const Person = ObjectViewMixin({ | ||
id: { type: 'uint32' }, | ||
name: { type: 'string', length: 10 }, | ||
}; | ||
}); | ||
@@ -94,18 +183,16 @@ // an array class for Person objects | ||
#### CollectionView | ||
Whereas ArrayView requires its contents to be of a specific ObjectView class, CollectionView allows holding objects and arrays | ||
of different types as well as being optional, i.e. it does not allocate space upon creation for missing members. | ||
Whereas a single ArrayView can only hold objects of a single ObjectView class, CollectionView allows holding objects and arrays | ||
of different types as well as them being optional, i.e. it does not allocate space upon creation for missing members. | ||
```javascript | ||
const { ObjectView, ArrayViewMixin, CollectionView } = require('structurae'); | ||
const { ObjectViewMixin, ArrayViewMixin, CollectionView } = require('structurae'); | ||
class Person extends ObjectView {} | ||
Person.schema = { | ||
const Person = ObjectViewMixin({ | ||
id: { type: 'uint32' }, | ||
name: { type: 'string', length: 10 }, | ||
}; | ||
}); | ||
class Pet extends ObjectView {} | ||
Pet.schema = { | ||
type: { type: 'string', length: 10 }, // // string with max length of 10 bytes | ||
}; | ||
const Pet = ObjectViewMixin({ | ||
type: { type: 'string', length: 10 }, // string with max length of 10 bytes | ||
}); | ||
@@ -128,52 +215,2 @@ const Pets = ArrayViewMixin(Pet); | ||
#### ObjectView | ||
Extends DataView to implement C-like struct. The fields are defined in ObjectView.schema an can be of any primitive type supported by DataView, | ||
their arrays, strings, or other objects and arrays of objects. | ||
```javascript | ||
class House extends ObjectView {} | ||
House.schema = { | ||
size: { type: 'uint32' }, // a primitive type | ||
}; | ||
class Pet extends ObjectView {} | ||
Pet.schema = { | ||
type: { type: 'string', length: 10 }, // // string with max length of 10 bytes | ||
}; | ||
class Person extends ObjectView {} | ||
Person.schema = { | ||
name: { type: 'string', length: 10 }, | ||
scores: { type: 'uint32', size: 10 }, // a an array of 10 numbers | ||
house: { type: House }, // another object view | ||
pets: { type: Pet, size: 3 }, // an array of 3 pet objects | ||
}; | ||
const person = Person.from({ | ||
name: 'Zaphod', | ||
scores: [1, 2, 3], | ||
house: { | ||
size: 1, | ||
}, | ||
pets: [ | ||
{ type: 'dog' }, { type: 'cat' } | ||
], | ||
}); | ||
person.byteLength | ||
//=> 44 | ||
person.get('scores').get(0) | ||
//=> 1 | ||
person.get('name') | ||
//=> StringView [10] | ||
person.getValue('name'); | ||
//=> Zaphod | ||
person.getValue('scores') | ||
//=> [1, 2, 3, 0, 0, 0, 0, 0, 0, 0,] | ||
person.set('house', { size: 5 }); | ||
person.getValue('house'); | ||
//=> { size: 5 } | ||
person.toJSON() | ||
//=> { name: 'Zaphod', scores: [1, 2, 3, 0, 0, 0, 0, 0, 0, 0,], house: { size: 5 }, pets: [{ type: 'dog' }, { type: 'cat' }, { type: '' }] } | ||
``` | ||
#### StringView | ||
@@ -227,27 +264,2 @@ Encoding API (available both in modern browsers and Node.js) allows us to convert JavaScript strings to | ||
#### StringArrayView | ||
An array of StringViews. Operates on an array of strings stored in an ArrayBuffer. | ||
```javascript | ||
const { StringArrayView } = require('structurae'); | ||
// create a StringArrayView from a given array of strings with maximum string length of 4 bytes | ||
const list = StringArrayView.from(['a', 'bc', 'defg'], 4); | ||
list.get(0); | ||
//=> StringView []; | ||
list.getValue(0); | ||
//=> 'a' | ||
list.toJSON(); | ||
//=> ['a', 'bc', 'defg'] | ||
// create an empty array of 10 strings with maximum length of 5 bytes | ||
const emptyList = StringArrayView.of(3, 5); | ||
emptyList.getValue(0); | ||
//=> '' | ||
emptyList.set(0, 'ab').getValue(0); | ||
//=> 'ab' | ||
[...emptyList].map(i => i.toString()); | ||
//=> ['ab', '', ''] | ||
``` | ||
#### TypedArrayView | ||
@@ -257,4 +269,4 @@ TypedArrays in JavaScript have two limitations that make them cumbersome to use in conjunction with DataView. | ||
second, TypedArrays require their offset (byteOffset) to be a multiple of their element size (BYTES_PER_ELEMENT), | ||
which means that they often cannot "view" into existing ArrayBuffer starting from cirtain positions. | ||
TypedArrayViews are essentially TypedArrays that circumvent both issues by using DataView. | ||
which means that they often cannot "view" into existing ArrayBuffer starting from certain positions. | ||
TypedArrayViews are essentially TypedArrays that circumvent both issues by using the DataView interface. | ||
You can specify endianness and instantiate them at any position in an existing ArrayBuffer. | ||
@@ -261,0 +273,0 @@ TypedArrayViews are internally used by ObjectView to handle arrays of numbers, although, they can be used on their own: |
198985
5596
891