Skip to content

Commit bea6c0e

Browse files
committed
Note about using package with TypeScript
1 parent 626a009 commit bea6c0e

File tree

2 files changed

+305
-6
lines changed

2 files changed

+305
-6
lines changed

README.md

Lines changed: 159 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,11 +266,167 @@ Client-side:
266266
</script>
267267
````
268268

269+
## Using with TypeScript
270+
The entire project is written in TypeScript, and declaration files are automatically generated. That means that if you are using TypeScript, the compiler can automatically infer the type of the various values exported from `structure-bytes`. To import the package, use:
271+
````javascript
272+
import * as sb from 'structure-bytes'
273+
````
274+
One of the most useful parts of having typings is that the compiler can automatically infer what types of valus a `Type` can serialize. For example:
275+
````javascript
276+
let type = new sb.StructType({
277+
abc: new sb.DoubleType,
278+
def: new sb.MapType(
279+
new sb.StringType,
280+
new sb.DayType
281+
)
282+
})
283+
type.valueBuffer(/*...*/)
284+
/*
285+
If you hover over "valueBuffer", you can see
286+
that it requires the value to be of type:
287+
{ abc: number | string, def: Map<string, Date> }
288+
*/
289+
````
290+
You can even add explicit `VALUE` generics to make the compiler check that your `Type` serializes the correct types of values.
291+
292+
With `StructType`:
293+
````typescript
294+
class Car {
295+
constructor(
296+
public make: string,
297+
public model: string,
298+
public year: number
299+
) {}
300+
}
301+
let structType = new sb.StructType<Car>({
302+
make: new sb.StringType,
303+
model: new sb.StringType,
304+
year: new sb.UnsignedShortType
305+
})
306+
//The compiler would have complained if one of the fields were missing
307+
//or one of the field's types didn't match the type of the field's values,
308+
//e.g. "make: new sb.BooleanType"
309+
````
310+
If you have transient fields (i.e. they shouldn't be serialized), you can create a separate interface for the fields that should be serialized:
311+
````typescript
312+
interface SerializedCar {
313+
make: string
314+
model: string
315+
year: number
316+
}
317+
class Car implements SerializedCar {
318+
public id: number //transient field
319+
constructor(public make: string, public model: string, public year: number) {
320+
this.id = Math.floor(Math.random() * 1000000)
321+
}
322+
}
323+
let structType = new sb.StructType<SerializedCar>({
324+
make: new sb.StringType,
325+
model: new sb.StringType,
326+
year: new sb.UnsignedShortType
327+
})
328+
````
329+
330+
With `ChoiceType` (and similarly for `NamedChoiceType`), you can let the type be inferred automatically if each of the possible types writes the same type of values:
331+
````javascript
332+
let type = new sb.ChoiceType([ //Type<number | string>
333+
new sb.ByteType,
334+
new sb.ShortType,
335+
new sb.IntType
336+
])
337+
````
338+
However, if the value types are not the same, TypeScript will complain about them not matching, and you should express the union type explicitly:
339+
````typescript
340+
interface RGB {
341+
r: number
342+
g: number
343+
b: number
344+
}
345+
interface HSV {
346+
h: number
347+
s: number
348+
v: number
349+
}
350+
type CSSColor = string
351+
352+
let choiceType = new sb.ChoiceType<RGB | HSV | CSSColor>([
353+
new sb.StructType<RGB>({
354+
r: new sb.FloatType,
355+
g: new sb.FloatType,
356+
b: new sb.FloatType
357+
}),
358+
new sb.StructType<HSV>({
359+
h: new sb.FloatType,
360+
s: new sb.FloatType,
361+
v: new sb.FloatType
362+
}),
363+
new sb.StringType
364+
])
365+
````
366+
A `RecursiveType` has no way to infer its value type, so you should always provide the `VALUE` generic:
367+
````typescript
368+
interface Cons<A> {
369+
head: A
370+
tail: List<A>
371+
}
372+
interface List<A> {
373+
list: Cons<A> | null //null for empty list
374+
}
375+
376+
let recursiveType = new sb.RecursiveType<List<string>>('linked-list')
377+
sb.registerType({
378+
type: new sb.StructType<List<string>>({
379+
list: new sb.OptionalType(
380+
new sb.StructType<Cons<string>>({
381+
head: new sb.StringType,
382+
tail: recursiveType
383+
})
384+
)
385+
}),
386+
name: 'linked-list'
387+
})
388+
recursiveType.valueBuffer({
389+
list: {
390+
head: '1',
391+
tail: {
392+
list: {
393+
head: '2',
394+
tail: {list: null}
395+
}
396+
}
397+
}
398+
})
399+
````
400+
When reading types from buffers and streams, they will be of type `Type<any>`.
401+
You should specify their value types before using them to write values:
402+
````typescript
403+
let booleanType = new sb.BooleanType
404+
let readType = sb.r.type(booleanType.toBuffer()) //Type<any>
405+
//Will throw a runtime error
406+
readType.valueBuffer('abc')
407+
408+
//vs.
409+
410+
let castReadType: sb.Type<boolean> = sb.r.type(booleanType.toBuffer())
411+
//Will throw a compiler error
412+
castReadType.valueBuffer('abc')
413+
````
414+
415+
It may also sometimes be useful to be more specific about what types of values you want to be able to serialize.
416+
For example, if you want to serialize integer values that will always be represented as numbers (and never in string form), you can force the compiler to error out if you try to serialize a string value with the following:
417+
````typescript
418+
let intType: sb.Type<number> = new sb.IntType
419+
//Now this is valid:
420+
intType.valueBuffer(100)
421+
//But this is not, even though it would be if you omitted the type annotation on intType:
422+
intType.valueBuffer('100')
423+
````
424+
269425
## Binary formats
270426
In the following definitions, `uint8_t` means an 8-bit unsigned integer. `flexInt` means a variable-length unsigned integer with the following format, where `X` represents either `0` or `1`:
271-
- `[0b0XXXXXXX]` stores values from `0` to `2^7 - 1` in their unsigned 7-bit integer representations
272-
- `[0b10XXXXXX, 0bXXXXXXXX]` stores values from `2^7` to `2^7 + 2^14 - 1`, where a value `x` is encoded into the unsigned 14-bit representation of `x - 2^7`
273-
- `[0b110XXXXX, 0bXXXXXXXX, 0bXXXXXXXX]` stores values from `2^7 + 2^14` to `2^7 + 2^14 + 2^21 - 1`, where a value `x` is encoded into the unsigned 14-bit representation of `x - (2^7 + 2^14)`
427+
- `[0b0XXXXXXX]` stores values from `0` to `2**7 - 1` in their unsigned 7-bit integer representations
428+
- `[0b10XXXXXX, 0bXXXXXXXX]` stores values from `2**7` to `2**7 + 2**14 - 1`, where a value `x` is encoded into the unsigned 14-bit representation of `x - 2**7`
429+
- `[0b110XXXXX, 0bXXXXXXXX, 0bXXXXXXXX]` stores values from `2**7 + 2**14` to `2**7 + 2**14 + 2**21 - 1`, where a value `x` is encoded into the unsigned 21-bit representation of `x - (2**7 + 2**14)`
274430
- and so on, up to 8-byte representations
275431

276432
All numbers are stored in big-endian format.

docs/index.html

Lines changed: 146 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,13 +326,156 @@ <h3 id="http-post-value">HTTP POST value</h3>
326326
});
327327
});
328328
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
329+
</code></pre>
330+
<h2 id="using-with-typescript">Using with TypeScript</h2>
331+
<p>The entire project is written in TypeScript, and declaration files are automatically generated. That means that if you are using TypeScript, the compiler can automatically infer the type of the various values exported from <code>structure-bytes</code>. To import the package, use:</p>
332+
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> sb <span class="hljs-keyword">from</span> <span class="hljs-string">'structure-bytes'</span>
333+
</code></pre>
334+
<p>One of the most useful parts of having typings is that the compiler can automatically infer what types of valus a <code>Type</code> can serialize. For example:</p>
335+
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> type = <span class="hljs-keyword">new</span> sb.StructType({
336+
<span class="hljs-attr">abc</span>: <span class="hljs-keyword">new</span> sb.DoubleType,
337+
<span class="hljs-attr">def</span>: <span class="hljs-keyword">new</span> sb.MapType(
338+
<span class="hljs-keyword">new</span> sb.StringType,
339+
<span class="hljs-keyword">new</span> sb.DayType
340+
)
341+
})
342+
type.valueBuffer(<span class="hljs-comment">/*...*/</span>)
343+
<span class="hljs-comment">/*
344+
If you hover over "valueBuffer", you can see
345+
that it requires the value to be of type:
346+
{ abc: number | string, def: Map&lt;string, Date&gt; }
347+
*/</span>
348+
</code></pre>
349+
<p>You can even add explicit <code>VALUE</code> generics to make the compiler check that your <code>Type</code> serializes the correct types of values.</p>
350+
<p>With <code>StructType</code>:</p>
351+
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> Car {
352+
<span class="hljs-keyword">constructor</span>(<span class="hljs-params">
353+
<span class="hljs-keyword">public</span> make: <span class="hljs-built_in">string</span>,
354+
<span class="hljs-keyword">public</span> model: <span class="hljs-built_in">string</span>,
355+
<span class="hljs-keyword">public</span> year: <span class="hljs-built_in">number</span>
356+
</span>) {}
357+
}
358+
<span class="hljs-keyword">let</span> structType = <span class="hljs-keyword">new</span> sb.StructType&lt;Car&gt;({
359+
make: <span class="hljs-keyword">new</span> sb.StringType,
360+
model: <span class="hljs-keyword">new</span> sb.StringType,
361+
year: <span class="hljs-keyword">new</span> sb.UnsignedShortType
362+
})
363+
<span class="hljs-comment">//The compiler would have complained if one of the fields were missing</span>
364+
<span class="hljs-comment">//or one of the field's types didn't match the type of the field's values,</span>
365+
<span class="hljs-comment">//e.g. "make: new sb.BooleanType"</span>
366+
</code></pre>
367+
<p>If you have transient fields (i.e. they shouldn&#39;t be serialized), you can create a separate interface for the fields that should be serialized:</p>
368+
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> SerializedCar {
369+
make: <span class="hljs-built_in">string</span>
370+
model: <span class="hljs-built_in">string</span>
371+
year: <span class="hljs-built_in">number</span>
372+
}
373+
<span class="hljs-keyword">class</span> Car <span class="hljs-keyword">implements</span> SerializedCar {
374+
<span class="hljs-keyword">public</span> id: <span class="hljs-built_in">number</span> <span class="hljs-comment">//transient field</span>
375+
<span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">public</span> make: <span class="hljs-built_in">string</span>, <span class="hljs-keyword">public</span> model: <span class="hljs-built_in">string</span>, <span class="hljs-keyword">public</span> year: <span class="hljs-built_in">number</span></span>) {
376+
<span class="hljs-keyword">this</span>.id = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * <span class="hljs-number">1000000</span>)
377+
}
378+
}
379+
<span class="hljs-keyword">let</span> structType = <span class="hljs-keyword">new</span> sb.StructType&lt;SerializedCar&gt;({
380+
make: <span class="hljs-keyword">new</span> sb.StringType,
381+
model: <span class="hljs-keyword">new</span> sb.StringType,
382+
year: <span class="hljs-keyword">new</span> sb.UnsignedShortType
383+
})
384+
</code></pre>
385+
<p>With <code>ChoiceType</code> (and similarly for <code>NamedChoiceType</code>), you can let the type be inferred automatically if each of the possible types writes the same type of values:</p>
386+
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> type = <span class="hljs-keyword">new</span> sb.ChoiceType([ <span class="hljs-comment">//Type&lt;number | string&gt;</span>
387+
<span class="hljs-keyword">new</span> sb.ByteType,
388+
<span class="hljs-keyword">new</span> sb.ShortType,
389+
<span class="hljs-keyword">new</span> sb.IntType
390+
])
391+
</code></pre>
392+
<p>However, if the value types are not the same, TypeScript will complain about them not matching, and you should express the union type explicitly:</p>
393+
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> RGB {
394+
r: <span class="hljs-built_in">number</span>
395+
g: <span class="hljs-built_in">number</span>
396+
b: <span class="hljs-built_in">number</span>
397+
}
398+
<span class="hljs-keyword">interface</span> HSV {
399+
h: <span class="hljs-built_in">number</span>
400+
s: <span class="hljs-built_in">number</span>
401+
v: <span class="hljs-built_in">number</span>
402+
}
403+
<span class="hljs-keyword">type</span> CSSColor = <span class="hljs-built_in">string</span>
404+
405+
<span class="hljs-keyword">let</span> choiceType = <span class="hljs-keyword">new</span> sb.ChoiceType&lt;RGB | HSV | CSSColor&gt;([
406+
<span class="hljs-keyword">new</span> sb.StructType&lt;RGB&gt;({
407+
r: <span class="hljs-keyword">new</span> sb.FloatType,
408+
g: <span class="hljs-keyword">new</span> sb.FloatType,
409+
b: <span class="hljs-keyword">new</span> sb.FloatType
410+
}),
411+
<span class="hljs-keyword">new</span> sb.StructType&lt;HSV&gt;({
412+
h: <span class="hljs-keyword">new</span> sb.FloatType,
413+
s: <span class="hljs-keyword">new</span> sb.FloatType,
414+
v: <span class="hljs-keyword">new</span> sb.FloatType
415+
}),
416+
<span class="hljs-keyword">new</span> sb.StringType
417+
])
418+
</code></pre>
419+
<p>A <code>RecursiveType</code> has no way to infer its value type, so you should always provide the <code>VALUE</code> generic:</p>
420+
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> Cons&lt;A&gt; {
421+
head: A
422+
tail: List&lt;A&gt;
423+
}
424+
<span class="hljs-keyword">interface</span> List&lt;A&gt; {
425+
list: Cons&lt;A&gt; | <span class="hljs-literal">null</span> <span class="hljs-comment">//null for empty list</span>
426+
}
427+
428+
<span class="hljs-keyword">let</span> recursiveType = <span class="hljs-keyword">new</span> sb.RecursiveType&lt;List&lt;<span class="hljs-built_in">string</span>&gt;&gt;(<span class="hljs-string">'linked-list'</span>)
429+
sb.registerType({
430+
<span class="hljs-keyword">type</span>: <span class="hljs-keyword">new</span> sb.StructType&lt;List&lt;<span class="hljs-built_in">string</span>&gt;&gt;({
431+
list: <span class="hljs-keyword">new</span> sb.OptionalType(
432+
<span class="hljs-keyword">new</span> sb.StructType&lt;Cons&lt;<span class="hljs-built_in">string</span>&gt;&gt;({
433+
head: <span class="hljs-keyword">new</span> sb.StringType,
434+
tail: recursiveType
435+
})
436+
)
437+
}),
438+
name: <span class="hljs-string">'linked-list'</span>
439+
})
440+
recursiveType.valueBuffer({
441+
list: {
442+
head: <span class="hljs-string">'1'</span>,
443+
tail: {
444+
list: {
445+
head: <span class="hljs-string">'2'</span>,
446+
tail: {list: <span class="hljs-literal">null</span>}
447+
}
448+
}
449+
}
450+
})
451+
</code></pre>
452+
<p>When reading types from buffers and streams, they will be of type <code>Type&lt;any&gt;</code>.
453+
You should specify their value types before using them to write values:</p>
454+
<pre><code class="lang-typescript"><span class="hljs-keyword">let</span> booleanType = <span class="hljs-keyword">new</span> sb.BooleanType
455+
<span class="hljs-keyword">let</span> readType = sb.r.type(booleanType.toBuffer()) <span class="hljs-comment">//Type&lt;any&gt;</span>
456+
<span class="hljs-comment">//Will throw a runtime error</span>
457+
readType.valueBuffer(<span class="hljs-string">'abc'</span>)
458+
459+
<span class="hljs-comment">//vs.</span>
460+
461+
<span class="hljs-keyword">let</span> castReadType: sb.Type&lt;<span class="hljs-built_in">boolean</span>&gt; = sb.r.type(booleanType.toBuffer())
462+
<span class="hljs-comment">//Will throw a compiler error</span>
463+
castReadType.valueBuffer(<span class="hljs-string">'abc'</span>)
464+
</code></pre>
465+
<p>It may also sometimes be useful to be more specific about what types of values you want to be able to serialize.
466+
For example, if you want to serialize integer values that will always be represented as numbers (and never in string form), you can force the compiler to error out if you try to serialize a string value with the following:</p>
467+
<pre><code class="lang-typescript"><span class="hljs-keyword">let</span> intType: sb.Type&lt;<span class="hljs-built_in">number</span>&gt; = <span class="hljs-keyword">new</span> sb.IntType
468+
<span class="hljs-comment">//Now this is valid:</span>
469+
intType.valueBuffer(<span class="hljs-number">100</span>)
470+
<span class="hljs-comment">//But this is not, even though it would be if you omitted the type annotation on intType:</span>
471+
intType.valueBuffer(<span class="hljs-string">'100'</span>)
329472
</code></pre>
330473
<h2 id="binary-formats">Binary formats</h2>
331474
<p>In the following definitions, <code>uint8_t</code> means an 8-bit unsigned integer. <code>flexInt</code> means a variable-length unsigned integer with the following format, where <code>X</code> represents either <code>0</code> or <code>1</code>:</p>
332475
<ul>
333-
<li><code>[0b0XXXXXXX]</code> stores values from <code>0</code> to <code>2^7 - 1</code> in their unsigned 7-bit integer representations</li>
334-
<li><code>[0b10XXXXXX, 0bXXXXXXXX]</code> stores values from <code>2^7</code> to <code>2^7 + 2^14 - 1</code>, where a value <code>x</code> is encoded into the unsigned 14-bit representation of <code>x - 2^7</code></li>
335-
<li><code>[0b110XXXXX, 0bXXXXXXXX, 0bXXXXXXXX]</code> stores values from <code>2^7 + 2^14</code> to <code>2^7 + 2^14 + 2^21 - 1</code>, where a value <code>x</code> is encoded into the unsigned 14-bit representation of <code>x - (2^7 + 2^14)</code></li>
476+
<li><code>[0b0XXXXXXX]</code> stores values from <code>0</code> to <code>2**7 - 1</code> in their unsigned 7-bit integer representations</li>
477+
<li><code>[0b10XXXXXX, 0bXXXXXXXX]</code> stores values from <code>2**7</code> to <code>2**7 + 2**14 - 1</code>, where a value <code>x</code> is encoded into the unsigned 14-bit representation of <code>x - 2**7</code></li>
478+
<li><code>[0b110XXXXX, 0bXXXXXXXX, 0bXXXXXXXX]</code> stores values from <code>2**7 + 2**14</code> to <code>2**7 + 2**14 + 2**21 - 1</code>, where a value <code>x</code> is encoded into the unsigned 21-bit representation of <code>x - (2**7 + 2**14)</code></li>
336479
<li>and so on, up to 8-byte representations</li>
337480
</ul>
338481
<p>All numbers are stored in big-endian format.</p>

0 commit comments

Comments
 (0)