Skip to content

Commit 5e58d5c

Browse files
authored
refactor(experimental): add FixedSizeCodec and VariableSizeCodec types (#1883)
This PR slightly refactors the type definitions of `Encoders`, `Decoders` and `Codecs` by providing a `FixedSize*` and `VariableSize*` type variant for each of them. This allows us to provide type safety when codec factories expect fixed size or variable size codecs. For instance, `reverseEncoder({} as VariableSizeEncoder<string>)` will now throw a TypeScript error as the `reverseEncoder<T>()` function expects a `FixedSizeEncoder<T>`.
1 parent 7800e3b commit 5e58d5c

File tree

11 files changed

+395
-70
lines changed

11 files changed

+395
-70
lines changed

packages/codecs-core/src/__tests__/reverse-codec-test.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createDecoder, createEncoder, Decoder, Encoder } from '../codec';
1+
import { createDecoder, createEncoder } from '../codec';
22
import { fixCodec } from '../fix-codec';
33
import { reverseCodec, reverseDecoder, reverseEncoder } from '../reverse-codec';
44
import { b, base16 } from './__setup__';
@@ -27,13 +27,14 @@ describe('reverseCodec', () => {
2727
expect(s(4).read(b('aaaa00000001bbbb'), 2)).toStrictEqual(['01000000', 6]);
2828

2929
// Variable-size codec.
30+
// @ts-expect-error Reversed codec should be fixed-size.
3031
expect(() => reverseCodec(base16)).toThrow('Cannot reverse a codec of variable size');
3132
});
3233
});
3334

3435
describe('reverseEncoder', () => {
3536
it('can reverse the bytes of a fixed-size encoder', () => {
36-
const encoder: Encoder<number> = createEncoder({
37+
const encoder = createEncoder({
3738
fixedSize: 2,
3839
write: (value: number, bytes, offset) => {
3940
bytes.set([value, 0], offset);
@@ -44,20 +45,24 @@ describe('reverseEncoder', () => {
4445
const reversedEncoder = reverseEncoder(encoder);
4546
expect(reversedEncoder.fixedSize).toBe(2);
4647
expect(reversedEncoder.encode(42)).toStrictEqual(new Uint8Array([0, 42]));
48+
49+
// @ts-expect-error Reversed encoder should be fixed-size.
4750
expect(() => reverseEncoder(base16)).toThrow('Cannot reverse a codec of variable size');
4851
});
4952
});
5053

5154
describe('reverseDecoder', () => {
5255
it('can reverse the bytes of a fixed-size decoder', () => {
53-
const decoder: Decoder<string> = createDecoder({
56+
const decoder = createDecoder({
5457
fixedSize: 2,
5558
read: (bytes: Uint8Array, offset = 0) => [`${bytes[offset]}-${bytes[offset + 1]}`, offset + 2],
5659
});
5760

5861
const reversedDecoder = reverseDecoder(decoder);
5962
expect(reversedDecoder.fixedSize).toBe(2);
6063
expect(reversedDecoder.read(new Uint8Array([42, 0]), 0)).toStrictEqual(['0-42', 2]);
64+
65+
// @ts-expect-error Reversed decoder should be fixed-size.
6166
expect(() => reverseDecoder(base16)).toThrow('Cannot reverse a codec of variable size');
6267
});
6368
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {
2+
Codec,
3+
createCodec,
4+
createDecoder,
5+
createEncoder,
6+
Decoder,
7+
Encoder,
8+
FixedSizeCodec,
9+
FixedSizeDecoder,
10+
FixedSizeEncoder,
11+
VariableSizeCodec,
12+
VariableSizeDecoder,
13+
VariableSizeEncoder,
14+
} from '../codec';
15+
16+
{
17+
// [createEncoder]: It knows if the encoder is fixed size or variable size.
18+
createEncoder({
19+
fixedSize: 42,
20+
write: (_: string) => 1,
21+
}) satisfies FixedSizeEncoder<string>;
22+
createEncoder({
23+
fixedSize: null,
24+
variableSize: (_: string) => 42,
25+
write: (_: string) => 1,
26+
}) satisfies VariableSizeEncoder<string>;
27+
createEncoder({} as Encoder<string>) satisfies Encoder<string>;
28+
}
29+
30+
{
31+
// [createDecoder]: It knows if the decoder is fixed size or variable size.
32+
createDecoder({
33+
fixedSize: 42,
34+
read: (): [string, number] => ['', 1],
35+
}) satisfies FixedSizeDecoder<string>;
36+
createDecoder({
37+
fixedSize: null,
38+
read: (): [string, number] => ['', 1],
39+
}) satisfies VariableSizeDecoder<string>;
40+
createDecoder({} as Decoder<string>) satisfies Decoder<string>;
41+
}
42+
43+
{
44+
// [createCodec]: It knows if the codec is fixed size or variable size.
45+
createCodec({
46+
fixedSize: 42,
47+
read: (): [string, number] => ['', 1],
48+
write: (_: string) => 1,
49+
}) satisfies FixedSizeCodec<string>;
50+
createCodec({
51+
fixedSize: null,
52+
read: (): [string, number] => ['', 1],
53+
variableSize: (_: string) => 42,
54+
write: (_: string) => 1,
55+
}) satisfies VariableSizeCodec<string>;
56+
createCodec({} as Codec<string>) satisfies Codec<string>;
57+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {
2+
Codec,
3+
Decoder,
4+
Encoder,
5+
FixedSizeCodec,
6+
FixedSizeDecoder,
7+
FixedSizeEncoder,
8+
VariableSizeCodec,
9+
VariableSizeDecoder,
10+
VariableSizeEncoder,
11+
} from '../codec';
12+
import { combineCodec } from '../combine-codec';
13+
14+
{
15+
// [combineCodec]: It keeps track of the fixed or variable size types.
16+
combineCodec({} as FixedSizeEncoder<string>, {} as FixedSizeDecoder<string>) satisfies FixedSizeCodec<string>;
17+
combineCodec(
18+
{} as VariableSizeEncoder<string>,
19+
{} as VariableSizeDecoder<string>
20+
) satisfies VariableSizeCodec<string>;
21+
combineCodec({} as Encoder<string>, {} as Decoder<string>) satisfies Codec<string>;
22+
}
23+
24+
{
25+
// [combineCodec]: It authorizes From and To types that such that To extends From.
26+
combineCodec({} as Encoder<number | bigint>, {} as Decoder<bigint>) satisfies Codec<number | bigint, bigint>;
27+
combineCodec({} as Encoder<{ name: string }>, {} as Decoder<{ name: string; age: number }>) satisfies Codec<
28+
{ name: string },
29+
{ name: string; age: number }
30+
>;
31+
}
32+
33+
{
34+
// [combineCodec]: It forbids From and To types that are not compatible.
35+
// @ts-expect-error Expected a number decoder or a string encoder.
36+
combineCodec({} as Encoder<number>, {} as Decoder<string>) satisfies Codec<string>;
37+
// @ts-expect-error number | bigint is not assignable to bigint.
38+
combineCodec({} as Encoder<bigint>, {} as Decoder<number | bigint>) satisfies Codec<bigint, number | bigint>;
39+
// @ts-expect-error The decoded value does not extend the encoded value.
40+
combineCodec({} as Encoder<{ name: string; age: number }>, {} as Decoder<{ name: string }>) satisfies Codec<
41+
{ name: string; age: number },
42+
// @ts-expect-error Age property is missing.
43+
{ name: string }
44+
>;
45+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {
2+
Codec,
3+
Decoder,
4+
Encoder,
5+
FixedSizeCodec,
6+
FixedSizeDecoder,
7+
FixedSizeEncoder,
8+
VariableSizeCodec,
9+
VariableSizeDecoder,
10+
VariableSizeEncoder,
11+
} from '../codec';
12+
import { fixCodec, fixDecoder, fixEncoder } from '../fix-codec';
13+
14+
{
15+
// [fixEncoder]: It transforms any encoder into a fixed size encoder.
16+
fixEncoder({} as FixedSizeEncoder<string>, 42) satisfies FixedSizeEncoder<string>;
17+
fixEncoder({} as VariableSizeEncoder<string>, 42) satisfies FixedSizeEncoder<string>;
18+
fixEncoder({} as Encoder<string>, 42) satisfies FixedSizeEncoder<string>;
19+
}
20+
21+
{
22+
// [fixDecoder]: It transforms any decoder into a fixed size decoder.
23+
fixDecoder({} as FixedSizeDecoder<string>, 42) satisfies FixedSizeDecoder<string>;
24+
fixDecoder({} as VariableSizeDecoder<string>, 42) satisfies FixedSizeDecoder<string>;
25+
fixDecoder({} as Decoder<string>, 42) satisfies FixedSizeDecoder<string>;
26+
}
27+
28+
{
29+
// [fixCodec]: It transforms any codec into a fixed size codec.
30+
fixCodec({} as FixedSizeCodec<string>, 42) satisfies FixedSizeCodec<string>;
31+
fixCodec({} as VariableSizeCodec<string>, 42) satisfies FixedSizeCodec<string>;
32+
fixCodec({} as Codec<string>, 42) satisfies FixedSizeCodec<string>;
33+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {
2+
Codec,
3+
Decoder,
4+
Encoder,
5+
FixedSizeCodec,
6+
FixedSizeDecoder,
7+
FixedSizeEncoder,
8+
VariableSizeCodec,
9+
VariableSizeDecoder,
10+
VariableSizeEncoder,
11+
} from '../codec';
12+
import { mapCodec, mapDecoder, mapEncoder } from '../map-codec';
13+
14+
{
15+
// [mapEncoder]: It keeps track of the nested encoder's size.
16+
mapEncoder({} as FixedSizeEncoder<string>, (_: number) => '42') satisfies FixedSizeEncoder<number>;
17+
mapEncoder({} as VariableSizeEncoder<string>, (_: number) => '42') satisfies VariableSizeEncoder<number>;
18+
mapEncoder({} as Encoder<string>, (_: number) => '42') satisfies Encoder<number>;
19+
}
20+
21+
{
22+
// [mapDecoder]: It keeps track of the nested decoder's size.
23+
mapDecoder({} as FixedSizeDecoder<string>, (_: string) => 42) satisfies FixedSizeDecoder<number>;
24+
mapDecoder({} as VariableSizeDecoder<string>, (_: string) => 42) satisfies VariableSizeDecoder<number>;
25+
mapDecoder({} as Decoder<string>, (_: string) => 42) satisfies Decoder<number>;
26+
}
27+
28+
{
29+
// [mapCodec]: It keeps track of the nested codec's size.
30+
mapCodec(
31+
{} as FixedSizeCodec<string>,
32+
(_: number) => '42',
33+
(_: string) => 42
34+
) satisfies FixedSizeCodec<number>;
35+
mapCodec(
36+
{} as VariableSizeCodec<string>,
37+
(_: number) => '42',
38+
(_: string) => 42
39+
) satisfies VariableSizeCodec<number>;
40+
mapCodec(
41+
{} as Codec<string>,
42+
(_: number) => '42',
43+
(_: string) => 42
44+
) satisfies Codec<number>;
45+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
Codec,
3+
Decoder,
4+
Encoder,
5+
FixedSizeCodec,
6+
FixedSizeDecoder,
7+
FixedSizeEncoder,
8+
VariableSizeCodec,
9+
VariableSizeDecoder,
10+
VariableSizeEncoder,
11+
} from '../codec';
12+
import { reverseCodec, reverseDecoder, reverseEncoder } from '../reverse-codec';
13+
14+
{
15+
// [reverseEncoder]: It only works with fixed size encoders.
16+
reverseEncoder({} as FixedSizeEncoder<string>) satisfies FixedSizeEncoder<string>;
17+
// @ts-expect-error Expected a fixed size encoder.
18+
reverseEncoder({} as VariableSizeEncoder<string>);
19+
// @ts-expect-error Expected a fixed size encoder.
20+
reverseEncoder({} as Encoder<string>);
21+
}
22+
23+
{
24+
// [reverseDecoder]: It only works with fixed size decoders.
25+
reverseDecoder({} as FixedSizeDecoder<string>) satisfies FixedSizeDecoder<string>;
26+
// @ts-expect-error Expected a fixed size decoder.
27+
reverseDecoder({} as VariableSizeDecoder<string>);
28+
// @ts-expect-error Expected a fixed size decoder.
29+
reverseDecoder({} as Decoder<string>);
30+
}
31+
32+
{
33+
// [reverseCodec]: It only works with fixed size codecs.
34+
reverseCodec({} as FixedSizeCodec<string>) satisfies FixedSizeCodec<string>;
35+
// @ts-expect-error Expected a fixed size codec.
36+
reverseCodec({} as VariableSizeCodec<string>);
37+
// @ts-expect-error Expected a fixed size codec.
38+
reverseCodec({} as Codec<string>);
39+
}

0 commit comments

Comments
 (0)