diff --git a/README.md b/README.md
index afba9b6..fc87076 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
# crunches :muscle:
-The smallest **and** fastest JavaScript web standards-compliant value serialization library in the wild. **3.06 kB** gzipped; **0 dependencies**. Efficiently encode and decode your values to and from `ArrayBuffer`s. Integrates very well with WebSockets.
+The smallest **and** fastest JavaScript web standards-compliant value serialization library in the wild. **3.24 kB** gzipped; **0 dependencies**. Efficiently encode and decode your values to and from `ArrayBuffer`s. Integrates very well with WebSockets.
## Example
@@ -205,24 +205,40 @@ Inside your codec, you must increment `target.byteOffset` as you decode bytes.
Just set a key on the `Codecs` object and go. Too easy!
+### Type aliases
+
+You may add type aliases:
+
+```js
+import {Aliases} from 'crunches';
+
+Aliases.foobar = 'bool';
+
+const schema = new Schema({
+ type: 'foobar',
+});
+
+console.log(schema.size()); // 1, because it's a bool.
+```
+
## Primitive types
-| Type Name | Bytes | Range of Values |
-|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------|
-| bool | 1 (worst case, see [boolean coalescence](#boolean-coalescence)) | Truthy values are coerced to `true`; falsy values to `false` |
-| int8 | 1 | -128 to 127 |
-| uint8 | 1 | 0 to 255 |
-| int16 | 2 | -32,768 to 32,767 |
-| uint16 | 2 | 0 to 65,535 |
-| int32 | 4 | -2,147,483,648 to 2,147,483,647 |
-| uint32 | 4 | 0 to 4,294,967,295 |
-| float32 | 4 | 3.4E +/- 38 (7 digits) |
-| float64 | 8 | 1.7E +/- 308 (15 digits) |
-| string | [Prefix](#varuint-prefixes) followed by the [encoded](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/encodeInto) string bytes | Any string |
-| buffer | [Prefix](#varuint-prefixes) followed by the bytes of the buffer | Any `ArrayBuffer`
Decodes to a `DataView`.
See: [buffers and arrays](#buffers-and-arrays). |
-| varuint |
size | min | max |
---|
1 | 0 | 127 |
2 | 128 | 16,383 |
3 | 16,384 | 2,097,151 |
4 | 2,097,152 | 268,435,455 |
5 | 268,435,456 | 34,359,738,367 |
6 | 34,359,738,368 | 4,398,046,511,103 |
7 | 4,398,046,511,104 | 562,949,953,421,311 |
| 0 to 562,949,953,421,311 |
-| varint | size | min | max |
---|
1 | -64 | 63 |
2 | -8,192 | 8,191 |
3 | -1,048,576 | 1,048,575 |
4 | -134,217,728 | 134,217,727 |
5 | -17,179,869,184 | 17,179,869,183 |
6 | -2,199,023,255,552 | 2,199,023,255,551 |
7 | -281,474,976,710,656 | 281,474,976,710,655 |
| -281,474,976,710,656 to 281,474,976,710,655 |
-| date | Same as `string` above after calling [`toIsoString`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) | Value is coerced to `Date` e.g. `new Date(value).toIsoString()` |
+| Type Name | Bytes | Range of Values |
+|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------|
+| bool (alias: boolean) | 1 (worst case, see [boolean coalescence](#boolean-coalescence)) | Truthy values are coerced to `true`; falsy values to `false` |
+| int8 | 1 | -128 to 127 |
+| uint8 | 1 | 0 to 255 |
+| int16 | 2 | -32,768 to 32,767 |
+| uint16 | 2 | 0 to 65,535 |
+| int32 | 4 | -2,147,483,648 to 2,147,483,647 |
+| uint32 | 4 | 0 to 4,294,967,295 |
+| float32 | 4 | 3.4E +/- 38 (7 digits) |
+| float64 | 8 | 1.7E +/- 308 (15 digits) |
+| string | [Prefix](#varuint-prefixes) followed by the [encoded](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/encodeInto) string bytes | Any string |
+| buffer | [Prefix](#varuint-prefixes) followed by the bytes of the buffer | Any `ArrayBuffer`
Decodes to a `DataView`.
See: [buffers and arrays](#buffers-and-arrays). |
+| varuint | size | min | max |
---|
1 | 0 | 127 |
2 | 128 | 16,383 |
3 | 16,384 | 2,097,151 |
4 | 2,097,152 | 268,435,455 |
5 | 268,435,456 | 34,359,738,367 |
6 | 34,359,738,368 | 4,398,046,511,103 |
7 | 4,398,046,511,104 | 562,949,953,421,311 |
| 0 to 562,949,953,421,311 |
+| varint | size | min | max |
---|
1 | -64 | 63 |
2 | -8,192 | 8,191 |
3 | -1,048,576 | 1,048,575 |
4 | -134,217,728 | 134,217,727 |
5 | -17,179,869,184 | 17,179,869,183 |
6 | -2,199,023,255,552 | 2,199,023,255,551 |
7 | -281,474,976,710,656 | 281,474,976,710,655 |
| -281,474,976,710,656 to 281,474,976,710,655 |
+| date | Same as `string` above after calling [`toIsoString`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) | Value is coerced to `Date` e.g. `new Date(value).toIsoString()` |
## Aggregate types
@@ -376,8 +392,6 @@ Instead of copying the data from the buffer, a [`TypedArray`](https://developer.
# TODO
-- Coalescence for boolean arrays?
-- Type aliases?
- BigInts?
- Endianness?
diff --git a/src/codecs.js b/src/codecs.js
index 56265b6..68de7cf 100644
--- a/src/codecs.js
+++ b/src/codecs.js
@@ -1 +1,23 @@
+export const Aliases = {
+ boolean: 'bool',
+};
+
export const Codecs = {};
+
+export function resolveCodec(blueprint) {
+ let {type} = blueprint;
+ if (undefined === type) {
+ throw new TypeError("No codec specified. Did you forget to include a 'type' key in your schema blueprint?");
+ }
+ const searched = new Set([type]);
+ let Codec = Codecs[type];
+ while (!Codec) {
+ type = Aliases[type];
+ if (!type || searched.has(type)) {
+ throw new TypeError(`Codec not found: '${blueprint.type}'`);
+ }
+ searched.add(type)
+ Codec = Codecs[type];
+ }
+ return new Codecs[type](blueprint);
+}
diff --git a/src/codecs.test.js b/src/codecs.test.js
new file mode 100644
index 0000000..65415fe
--- /dev/null
+++ b/src/codecs.test.js
@@ -0,0 +1,23 @@
+import {expect, test} from 'vitest';
+
+import BoolCodec from './codecs/bool.js';
+import {Aliases, Codecs, resolveCodec} from './codecs.js';
+
+Codecs.bool = BoolCodec;
+
+test('resolve', async () => {
+ expect(resolveCodec({type: 'bool'})).toBeInstanceOf(BoolCodec);
+});
+
+test('aliases', async () => {
+ expect(resolveCodec({type: 'boolean'})).toBeInstanceOf(BoolCodec);
+ Aliases.booboo = 'boolean';
+ expect(resolveCodec({type: 'booboo'})).toBeInstanceOf(BoolCodec);
+});
+
+test('alias cycle', async () => {
+ expect(resolveCodec({type: 'boolean'})).toBeInstanceOf(BoolCodec);
+ Aliases.foofoo = 'bar';
+ Aliases.bar = 'foofoo';
+ expect(() => resolveCodec({type: 'foofoo'})).toThrowError();
+});
diff --git a/src/codecs/array.js b/src/codecs/array.js
index 36145c4..5a2fa08 100644
--- a/src/codecs/array.js
+++ b/src/codecs/array.js
@@ -1,4 +1,4 @@
-import {Codecs} from '../codecs.js';
+import {resolveCodec} from '../codecs.js';
export function typeToElementClass(type) {
switch (type) {
@@ -27,12 +27,7 @@ class ArrayCodec {
$$elementCodec;
constructor(blueprint) {
- if (!(blueprint.element.type in Codecs)) {
- throw new TypeError(`No such codec '${blueprint.element.type}'`);
- }
- // todo: throw on optional or honor and encode sparse arrays
- // todo: boolean coalescence
- this.$$elementCodec = new Codecs[blueprint.element.type](blueprint.element);
+ this.$$elementCodec = resolveCodec(blueprint.element);
const {length = 0} = blueprint;
const {type} = blueprint.element;
const ElementClass = typeToElementClass(type);
diff --git a/src/codecs/object.js b/src/codecs/object.js
index 7028746..4f6bc36 100644
--- a/src/codecs/object.js
+++ b/src/codecs/object.js
@@ -1,4 +1,5 @@
-import {Codecs} from '../codecs.js';
+import {resolveCodec} from '../codecs.js';
+import BoolCodec from './bool.js';
class ObjectCodec {
@@ -14,15 +15,12 @@ class ObjectCodec {
let i = 0;
for (const key in blueprint.properties) {
const property = blueprint.properties[key];
- if (!(property.type in Codecs)) {
- throw new TypeError(`No such codec '${property.type}'`);
- }
- const codec = new Codecs[property.type](property);
+ const codec = resolveCodec(property);
if (property.optional) {
this.$$optionals += 1;
decoderCode += `
if (!(optionalFlags[currentOptional >> 3] & (1 << (currentOptional & 7)))) {
- ${'bool' === property.type ? '$$booleans -= 1' : ''}
+ ${(codec instanceof BoolCodec) ? '$$booleans -= 1' : ''}
}
else {
`;
@@ -33,7 +31,7 @@ class ObjectCodec {
if (isPresent) {
`;
}
- if ('bool' === property.type) {
+ if (codec instanceof BoolCodec) {
this.$$booleans += 1;
decoderCode += `
booleanBackpatches.push({bit: currentBoolean & 7, index: currentBoolean >> 3, key: '${key}'});
@@ -45,8 +43,8 @@ class ObjectCodec {
`;
}
else {
- decoderCode += `value['${key}'] = this.$$codecs[${i}].codec.decode(view, target);`;
- encoderCode += `written += this.$$codecs[${i}].codec.encode(value['${key}'], view, byteOffset + written);`;
+ decoderCode += `value['${key}'] = this.$$codecs[${i}].decode(view, target);`;
+ encoderCode += `written += this.$$codecs[${i}].encode(value['${key}'], view, byteOffset + written);`;
}
if (property.optional) {
decoderCode += `
@@ -55,7 +53,7 @@ class ObjectCodec {
`;
encoderCode += '}';
}
- this.$$codecs.push({codec, key, property});
+ this.$$codecs.push(codec);
i += 1;
}
if (this.$$booleans > 0) {
@@ -119,6 +117,29 @@ class ObjectCodec {
decoderCode += 'return value';
this.$$decode = new Function('view, target', decoderCode);
this.$$encode = new Function('value, view, byteOffset', encoderCode);
+ this.$$size = (value) => {
+ let {$$booleans} = this;
+ let size = 0;
+ size += Math.ceil(this.$$optionals / 8);
+ let i = 0;
+ for (const key in blueprint.properties) {
+ const codec = this.$$codecs[i];
+ const property = blueprint.properties[key];
+ if (property.optional && 'undefined' === typeof value[key]) {
+ if (codec instanceof BoolCodec) {
+ $$booleans -= 1;
+ }
+ i += 1;
+ continue;
+ }
+ if (!(codec instanceof BoolCodec)) {
+ size += codec.size(value[key]);
+ }
+ i += 1;
+ }
+ size += Math.ceil($$booleans / 8);
+ return size;
+ };
}
decode(view, target) {
@@ -130,22 +151,7 @@ class ObjectCodec {
}
size(value) {
- let {$$booleans} = this;
- let size = 0;
- size += Math.ceil(this.$$optionals / 8);
- for (const {codec, key, property} of this.$$codecs) {
- if (property.optional && 'undefined' === typeof value[key]) {
- if ('bool' === property.type) {
- $$booleans -= 1;
- }
- continue;
- }
- if ('bool' !== property.type) {
- size += codec.size(value[key]);
- }
- }
- size += Math.ceil($$booleans / 8);
- return size;
+ return this.$$size(value);
}
}
diff --git a/src/codecs/object.test.js b/src/codecs/object.test.js
index 389a53c..bf450b1 100644
--- a/src/codecs/object.test.js
+++ b/src/codecs/object.test.js
@@ -2,7 +2,7 @@ import {expect, test} from 'vitest';
import {Codecs} from '../codecs.js';
import Codec from './object.js';
-import BoolCodec from './uint8.js';
+import BoolCodec from './bool.js';
import Uint8Codec from './uint8.js';
Codecs.bool = BoolCodec;
@@ -17,7 +17,7 @@ test('object', async () => {
},
});
const view = new DataView(new ArrayBuffer(codec.size({1: 32, 2: 32})));
- codec.encode({1: 32, 2: 32}, view, 0);
+ expect(codec.encode({1: 32, 2: 32}, view, 0)).to.equal(2);
expect(codec.decode(view, {byteOffset: 0})).to.deep.equal({1: 32, 2: 32});
});
@@ -39,6 +39,24 @@ test('object boolean coalescence', async () => {
expect(codec.size(value)).to.equal(2);
});
+test('object aliased boolean coalescence', async () => {
+ let codec;
+ const blueprint = {properties: {}};
+ const value = {};
+ for (let i = 0; i < 8; ++i) {
+ blueprint.properties[i] = {type: 'boolean'};
+ value[i] = i;
+ }
+ codec = new Codec(blueprint);
+ expect(codec.size(value)).to.equal(1);
+ for (let i = 8; i < 16; ++i) {
+ blueprint.properties[i] = {type: 'boolean'};
+ value[i] = i;
+ }
+ codec = new Codec(blueprint);
+ expect(codec.size(value)).to.equal(2);
+});
+
test('object optional coalescence', async () => {
let codec;
const blueprint = {properties: {}};
diff --git a/src/index.js b/src/index.js
index 7091687..c9cafd0 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,4 @@
-import {Codecs} from './codecs.js';
+import {Aliases, Codecs} from './codecs.js';
import Schema from './schema.js';
const codecs = import.meta.glob(
@@ -10,4 +10,4 @@ for (const path in codecs) {
Codecs[key] = codecs[path];
}
-export {Codecs, Schema};
+export {Aliases, Codecs, Schema};
diff --git a/src/schema.js b/src/schema.js
index 3c72b19..57214c2 100644
--- a/src/schema.js
+++ b/src/schema.js
@@ -1,4 +1,4 @@
-import {Codecs} from './codecs.js';
+import {resolveCodec} from './codecs.js';
// Just an ergonomic wrapper around the root codec.
class Schema {
@@ -6,10 +6,7 @@ class Schema {
$$codec;
constructor(blueprint) {
- if (!(blueprint.type in Codecs)) {
- throw new TypeError(`No such codec '${blueprint.type}'`);
- }
- this.$$codec = new Codecs[blueprint.type](blueprint);
+ this.$$codec = resolveCodec(blueprint);
}
decode(view, target = {byteOffset: 0}) {