molecule-javascript v0.1.8
Molecule-JavaScript
Quick Start
There's a template project for quick start to use molecule-javascript.
It shows the most general way of using molecule-javascript in Browsers and Node.js with real-world demos.
Start
Use in Node.js
import Molecule from 'molecule-javascript'
const normalizedSchema = {
name: 'Bytes',
type: 'fixvec',
item: {
type: 'byte',
},
}
const data = ['0x01', '0x02']
const molecule = new Molecule(normalizedSchema)
const serialized = molecule.serialize(data) // expect to be "0x020000000102"
const parsed = molecule.deserialize('0x020000000102') // expect to be ["0x01", "0x02"]Use in Browsers
const normalizedSchema = {
name: 'Bytes',
type: 'fixvec',
item: {
type: 'byte',
},
}
const data = ['0x01', '0x02']
const molecule = new MoleculeJavaScript.Molecule(normalizedSchema) // The package is exposed as globalThis.MoleculeJavaScript
const serialized = molecule.serialize(data) // expect to be "0x020000000102"
const parsed = molecule.deserialize('0x020000000102') // expect to be ["0x01", "0x02"]Normalize Schema
Usually the schema will be defined with intermediate types in Molefile, e.g.
vector Word <byte>;
vector Words <Word>;There're two steps to normalize it.
1. Use Moleculec to transform Molefile into JSON file, which is valid in JavaScript
This step requires rust
$ cargo install moleculec
$ moleculec --language - --format json --schema-file ./types.mol > ./types.jsonBy the opeartion above, a new file named types.json will be generated with following content:
{
"namespace": "types",
"imports": [],
"declarations": [
{
"type": "fixvec",
"name": "Word",
"item": "byte"
},
{
"type": "dynvec",
"name": "Words",
"item": "Word"
}
]
}2. Normalize intermediate types with native types
$ npx moleculec-js -ns ./types.json > normalized-types.jsonThen the normalized-types.json is generated as follows
{
"namespace": "types",
"declarations": [
{
"type": "fixvec",
"name": "Word",
"item": {
"type": "byte"
}
},
{
"type": "dynvec",
"name": "Words",
"item": {
"type": "fixvec",
"item": {
"type": "byte"
}
}
}
]
}Shortcut for Node.js
The following three commands are equivalent.
moleculec-js ./schema.json molecules.js
# or
moleculec-js '{"namespace": "bytes", "declarations": [ { "name": "Bytes", "type": "fixvec","item": "byte" } ]}' molecules.js
# or used with moleculec
moleculec --language - --format json --schema-file ./schema.mol | moleculec-js > molecules.jsAnd molecule.js will be generated as
const { Molecule } = require('molecule-javascript')
const { Schema } = require('molecule-javascript/lib/schema')
/**
* moleculec-js ./schema.json molecules.js will use the file path directly
* const normalizedSchema = new Schema('./schema.json').getNormalizedSchema()
*/
const normalizedSchema = new Schema({
namespace: 'types',
imports: [],
declarations: [
{
type: 'fixvec',
name: 'Bytes',
item: 'byte',
},
],
}).getNormalizedSchema()
const molecules = {}
normalizedSchema.declarations.forEach(declaration => {
molecules[declaration.name] = new Molecule(declaration)
})
module.exports = { normalizedSchema, molecules }For Those Who Want More Info on Molecule
Molecule is a canonicalization and zero-copy serialization format.
Summary
Fixed Size or Dynamic Size
| Type | byte | array | struct | vector | table | option | union |
|---|---|---|---|---|---|---|---|
| Size | Fixed | Fixed | Fixed | Dynamic | Dynamic | Dynamic | Dynamic |
Memory Layout
| Type | Header | Body |
|--------+--------------------------------------------------+-----------------------------------|
| array | | item-0 | item-1 | ... | item-N |
| struct | | field-0 | field-1 | ... | field-N |
| fixvec | items-count | item-0 | item-1 | ... | item-N |
| dynvec | full-size | offset-0 | offset-1 | ... | offset-N | item-0 | item-1 | ... | item-N |
| table | full-size | offset-0 | offset-1 | ... | offset-N | filed-0 | field-1 | ... | field-N |
| option | | item or none (zero bytes) |
| union | item-type-id | item |Primitive Type
byte: thebyteis a byte- Example:
00is abyte
- Example:
Composite Types
array: Thearrayis a fixed-size type, it has a fixed_size inner type and a fixed length. The size of anarrayis the size of inner type times the length.| Type | Header | Body | Size | |--------+----------+-----------------------------------+------------------------------| | array | | item-0 | item-1 | ... | item-N | size-of-innner-type * length |To serialize an
arrayonly need to serialize all items in it. There's no overhead to serialize anarray, which stores all items consecutively without extra spaces between two adjacent items.Examples:
array Byte3 [byte; 3]stores01 02 03and is serialized as01 02 03array Uint32 [byte; 4]stores a 32-bit unsigned interger0x 01 02 03 04in little-endian, and is serialized as04 03 02 01array TwoUint32 [Uint32; 2]stores two 32-bit unsinged integers0x 01 02 03 04,0x 0a bc dein little-endian, and is serialized as04 03 02 01 de bc 0a 00
struct: Thestructis a fixed-size type, all fields instructare fixed-size and it has a fixed-size quantity of fields.. The size of astructis the sum of all fields' size.| Type | Header | Body | Size | |--------+----------+-----------------------------------+---------------------| | struct | | field-0 | field-1 | ... | field-N | Sum of fields' size |To serialize a
structonly need to serialize all fields of it. Fields in astructare stored in the order they are declared. There's no overhead to serialize astruct, which stores all fields consecutively without extra spaces between two adjacent items.Examples:
struct OnlyAByte { f1: byte }stores a byteaband is serialized asabstruct ByteAndUint32 { f1: byte, f2: Uint32 }stores{ f1: 'ab', f2: '0x010203' }(uint32 into little-endian) and is serialized asab 03 02 01 00
vector: There are two kinds of vectors:fixed-vector, we call itfixvec, anddynamic-vector, we call itdynvec.Whether a vector is fiexed or dynamic depends on the type of its inner item: if the inner item is a fixed-size, then the vector is a
fixvec; otherwise it's adynvecBut both of them are
dynamic-sizefixvec: fixed vector| Type | Header | Body | Size | |--------+-------------+-----------------------------------|------------------------------------| | fixvec | items-count | item-0 | item-1 | ... | item-N | 32 bits + inner-type-size * length |There're two steps to serialize a
fixvec:- Serialize the length as a uint32 in little-endian
- Serialize the items
Examples:
vector Bytes <byte>- the serialized bytes of an empty bytes is
00 00 00 00 |(the length of any empty fixed-vector is 0) - the serialized bytes of
0x12is01 00 00 00 | 12 - the serialzied bytes of
0x1234567890abcdefis08 00 00 00 | 12 34 56 78 90 ab cd ef
- the serialized bytes of an empty bytes is
vector Uint32Vec <Uint32>- the serialized bytes of empty
Uint32Vecis00 00 00 00 | - the serialized bytes of
0x123(or[0x123]) is01 00 00 00 | 23 01 00 00 - the serialized bytes of
[0x123 0x456, 0x7890, 0xa, 0xbc, 0xdef]is06 00 00 00 | 23 01 00 00 | 56 04 00 00 | 90 78 00 00 | 0a 00 00 00 | bc 00 00 00 | ef 0d 00 00
- the serialized bytes of empty
dynvec: dynamic vector| Type | Header | Body | Size | |--------+--------------------------------------------------+-----------------------------------|-----------------------------------------------------| | fixvec | full-size | offset-0 | offset-1 | ... | offset-N | item-0 | item-1 | ... | item-N | 32 bits + 32 bits * item count + sum of items' size |There're three steps to serialize a
dynvec- Serialize the full size in bytes as a 32-bit unsigned interger in little-endian
- Serialize all offsets of items as 32-bit unsigned integer in little-endian
- Serialize all items
Examples:
vector BytesVec <Bytes>(Bytes isfixvec <byte>)- the serizlied bytes of an empty
BytesVecis04 00 00 00 | - the serialied bytes of
[0x1234]is0e 00 00 00 | 08 00 00 00 | 02 00 00 00 12 34, where full size is 14, offset is 8, item list is 020000001234. - the serialized bytes of
[0x1234, 0x, 0x567, 0x89, 0xabcdef]isfull size: 34 00 00 00 // 4 + 4 * 5 items + 28 items size offsets : 18 00 00 00 1e 00 00 00 22 00 00 00 28 00 00 00 2d 00 00 00 items : 02 00 00 00 | 12 34 00 00 00 00 | 02 00 00 00 | 05 67 01 00 00 00 | 89 03 00 00 00 | ab cd ef
- the serizlied bytes of an empty
table: thetableis a dynamic-size type, and can be considered as adynvecbut the length is fixed.| Type | Header | Body | Size | |--------+--------------------------------------------------+-----------------------------------|-----------------------------------------------------| | table | full-size | offset-0 | offset-1 | ... | offset-N | filed-0 | field-1 | ... | field-N | 32 bits + 32 bits * item count + sum of items' size |The steps of serialization of
tableare same asdynvec:- Serialize the full size in bytes as 32-bit unsigned integer in little-endian
- Serialize all offsets of fields as 32-bit unsigned integer in little-endian
- Serialize all fields of the table in the order as they are declared
Examples
# table { f1: Bytes(0x), f2: byte(0xab), f3: uint32(0x123), f4: Byte3(0x456789), f5: Bytes(0xabcdef), } full size: 2b 00 00 00 // 4 + 4 * 5 items + (4 + 1 + 4 + 3 + 7) offsets : 18 00 00 00 1c 00 00 00 1d 00 00 00 21 00 00 00 24 00 00 00 items : 00 00 00 00 ab 23 01 00 00 45 67 89 03 00 00 00 | ab cd ef
option: Theoptionis a dynamic-size type.To serialize an
optiondepends on whether it is empty or not:- If it's empty, there'is zero bytes
- If it's not empty, just serialize the inner item(the size is the same as it's inner item's)
Examples:
- The serialized bytes of
Optionis(empty) - The serialized bytes of
Some([])is04 00 00 00 - The serialized bytes of
Some([0x])isfull size: 0c 00 00 00 offsets : 08 00 00 00 items : 00 00 00 00 |
- The serialized bytes of
union: Theunionis a dynamic-size type.Serialization of a
uniontype has two steps:- Serialize a item type id in bytes as a 32-bit unsigned integer in little-endian. The item type id is the index of the inner items, starting from 0.
- Serialize the inner item.
Examples: Suppose the type of the union is
{ Bytes3, Bytes, BytesVec, BytesVecOpt, }- The serialized bytes of
Byte3(0x123456)is00 00 00 00 | 12 34 56 - The serialized bytes of
Bytes(0x)is01 00 00 00 | 00 00 00 00 - The serialized bytes of
Bytes(0x123)is01 00 00 00 | 02 00 00 00 | 01 23 - The serialized bytes of
BytesVec([])is02 00 00 00 | 04 00 00 00 - The serialized bytes of
BytesVec([0x])is02 00 00 00 | 0c 00 00 00 | 08 00 00 00 | 00 00 00 00 - The serialized bytes of
BytesVec([0x123])is02 00 00 00 | 0e 00 00 00 | 08 00 00 00 | 02 00 00 00 | 01 12 - The serialized bytes of
BytesVec([0x123, 0x456])is02 00 00 00 | 18 00 00 00 | 0c 00 00 00 | 12 00 00 00 | 02 00 00 00 | 01 23 | 02 00 00 00 | 04 56
- The serialized bytes of
Schema to JSON
foo/types.mol
// Schema
array Word [byte; 2]
struct Struct1 {
f1: Word,
f2: byte,
}bar/types.mol
import ../foo/types;
vector Bytes <byte>
vector BytesVec <Bytes>
option ByteOpt (byte)
table Table1 {
f1: Bytes,
f2: byte,
f3: ByteOpt,
}
union UnionA {
Bytes,
Struct1,
byte,
Table1,
}intermediate for bar/types.mol
// JSON
{
"namespace": "types",
"imports": [
{
"name": "types",
"paths": ["foo"],
"path_supers": 1
}
],
"declarations": [
{
"type": "fixvec",
"name": "Bytes",
"item": "byte"
},
{
"type": "dynvec",
"name": "BytesVec",
"item": "Bytes"
},
{
"type": "option",
"name": "ByteOpt",
"item": "byte"
},
{
"type": "table",
"name": "Table1",
"fields": [
{
"name": "f1",
"type": "Bytes"
},
{
"name": "f2",
"type": "byte"
},
{
"name": "f3",
"type": "ByteOpt"
}
]
},
{
"type": "union",
"name": "UnionA",
"items": ["Bytes", "Struct1", "byte", "Table1"]
},
{
"type": "array",
"name": "Word",
"item": "byte",
"item_count": 2,
"imported_depth": 1
},
{
"type": "struct",
"name": "Struct1",
"fields": [
{
"name": "f1",
"type": "Word"
},
{
"name": "f2",
"type": "byte"
}
],
"imported_depth": 1
}
]
}