@nnilky/structo 1.0.5 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/datatypes/containers/array.js +9 -0
- package/dist/datatypes/containers/array.test.js +6 -10
- package/dist/datatypes/containers/exhuastiveArray.d.ts +2 -2
- package/dist/datatypes/containers/exhuastiveArray.js +13 -2
- package/dist/datatypes/containers/exhuastiveArray.test.js +9 -12
- package/dist/datatypes/containers/fastObject.d.ts +3 -1
- package/dist/datatypes/containers/fastObject.js +3 -1
- package/dist/datatypes/containers/fastObject.test.js +18 -6
- package/dist/datatypes/containers/list.js +14 -2
- package/dist/datatypes/containers/list.test.js +6 -12
- package/dist/datatypes/containers/object.js +8 -2
- package/dist/datatypes/containers/object.test.js +22 -10
- package/dist/datatypes/containers/taggedUnion.d.ts +12 -8
- package/dist/datatypes/containers/taggedUnion.js +4 -2
- package/dist/datatypes/containers/taggedUnion.test.js +22 -18
- package/dist/datatypes/index.d.ts +2 -9
- package/dist/datatypes/index.js +2 -9
- package/dist/datatypes/numbers/bigints.test.js +11 -11
- package/dist/datatypes/numbers/floats.test.js +6 -6
- package/dist/datatypes/numbers/sints.test.js +11 -11
- package/dist/datatypes/numbers/uints.test.js +11 -11
- package/dist/datatypes/values/bytes.test.js +6 -8
- package/dist/datatypes/values/json.d.ts +10 -0
- package/dist/datatypes/values/json.js +21 -0
- package/dist/datatypes/values/sizedbytes.d.ts +2 -0
- package/dist/datatypes/values/sizedbytes.js +17 -0
- package/dist/datatypes/values/sizedbytes.test.d.ts +1 -0
- package/dist/datatypes/values/sizedbytes.test.js +25 -0
- package/dist/datatypes/values/string.d.ts +14 -0
- package/dist/datatypes/values/string.js +36 -0
- package/dist/datatypes/values/string.test.d.ts +1 -0
- package/dist/datatypes/values/string.test.js +21 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/read.d.ts +2 -6
- package/dist/read.js +9 -1
- package/dist/transforms/encode.d.ts +5 -0
- package/dist/transforms/encode.js +8 -0
- package/dist/transforms/enum.d.ts +2 -0
- package/dist/transforms/enum.js +16 -0
- package/dist/transforms/enum.test.d.ts +1 -0
- package/dist/transforms/enum.test.js +21 -0
- package/dist/transforms/fixedOffset.d.ts +2 -0
- package/dist/transforms/fixedOffset.js +18 -0
- package/dist/transforms/index.d.ts +11 -0
- package/dist/transforms/index.js +11 -0
- package/dist/transforms/literal.d.ts +1 -0
- package/dist/transforms/literal.js +15 -0
- package/dist/transforms/literal.test.d.ts +1 -0
- package/dist/transforms/literal.test.js +23 -0
- package/dist/transforms/modify.d.ts +20 -0
- package/dist/transforms/modify.js +27 -0
- package/dist/transforms/modify.test.d.ts +1 -0
- package/dist/transforms/modify.test.js +17 -0
- package/dist/transforms/noAdvance.d.ts +16 -0
- package/dist/transforms/noAdvance.js +30 -0
- package/dist/transforms/noAdvance.test.d.ts +1 -0
- package/dist/transforms/noAdvance.test.js +17 -0
- package/dist/transforms/offset.d.ts +2 -0
- package/dist/transforms/offset.js +20 -0
- package/dist/transforms/offset.test.d.ts +1 -0
- package/dist/transforms/offset.test.js +39 -0
- package/dist/transforms/pipe.d.ts +11 -0
- package/dist/transforms/pipe.js +16 -0
- package/dist/transforms/toAscii.d.ts +1 -0
- package/dist/transforms/toAscii.js +14 -0
- package/dist/transforms/toAscii.test.d.ts +1 -0
- package/dist/transforms/toAscii.test.js +25 -0
- package/dist/transforms/toBase64.d.ts +1 -0
- package/dist/transforms/toBase64.js +7 -0
- package/dist/transforms/toBase64.test.d.ts +1 -0
- package/dist/transforms/toBase64.test.js +27 -0
- package/dist/transforms/toBytes.d.ts +13 -0
- package/dist/transforms/toBytes.js +24 -0
- package/dist/transforms/toBytes.test.d.ts +1 -0
- package/dist/transforms/toBytes.test.js +27 -0
- package/dist/transforms/toHex.d.ts +13 -0
- package/dist/transforms/toHex.js +19 -0
- package/dist/transforms/toHex.test.d.ts +1 -0
- package/dist/transforms/toHex.test.js +23 -0
- package/dist/transforms/toTypedArray.d.ts +16 -0
- package/dist/transforms/toTypedArray.js +17 -0
- package/dist/transforms/toTypedArray.test.d.ts +1 -0
- package/dist/transforms/toTypedArray.test.js +29 -0
- package/dist/types.d.ts +3 -0
- package/dist/utilities/index.d.ts +3 -0
- package/dist/utilities/index.js +3 -0
- package/dist/utilities/lazy.d.ts +2 -0
- package/dist/utilities/lazy.js +25 -0
- package/dist/utilities/lazy.test.d.ts +1 -0
- package/dist/utilities/lazy.test.js +36 -0
- package/dist/utilities/reference.d.ts +18 -0
- package/dist/utilities/reference.js +91 -0
- package/dist/utilities/reference.test.d.ts +1 -0
- package/dist/utilities/reference.test.js +63 -0
- package/dist/utilities/remember.d.ts +18 -0
- package/dist/utilities/remember.js +75 -0
- package/dist/utilities/remember.test.d.ts +1 -0
- package/dist/utilities/remember.test.js +72 -0
- package/dist/utils.test.d.ts +9 -1
- package/dist/utils.test.js +42 -1
- package/dist/write.js +9 -1
- package/package.json +19 -14
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function offset(behaviour, offset) {
|
|
2
|
+
return (type) => ({
|
|
3
|
+
size: type.size,
|
|
4
|
+
read: (ctx) => {
|
|
5
|
+
let start = ctx.offset;
|
|
6
|
+
const offsetValue = typeof offset === "number" ? offset : offset.read(ctx);
|
|
7
|
+
ctx.offset = behaviour === "absolute" ? offsetValue : ctx.offset + offsetValue;
|
|
8
|
+
const value = type.read(ctx);
|
|
9
|
+
ctx.offset = start;
|
|
10
|
+
return value;
|
|
11
|
+
},
|
|
12
|
+
write: (ctx, value) => {
|
|
13
|
+
let start = ctx.offset;
|
|
14
|
+
const offsetValue = typeof offset === "number" ? offset : offset.read(ctx);
|
|
15
|
+
ctx.offset = behaviour === "absolute" ? offsetValue : ctx.offset + offsetValue;
|
|
16
|
+
type.write(ctx, value);
|
|
17
|
+
ctx.offset = start;
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, it } from "bun:test";
|
|
2
|
+
import { encodeTest } from "../utils.test";
|
|
3
|
+
import * as st from "../index";
|
|
4
|
+
describe("st.offset(relative)", () => {
|
|
5
|
+
it("encodes correctly with fixed offset", () => {
|
|
6
|
+
const spec = st.object({
|
|
7
|
+
foo: st.u32(),
|
|
8
|
+
bar: st.pipe(st.u32(), st.offset("relative", -4)),
|
|
9
|
+
});
|
|
10
|
+
encodeTest(spec, { foo: 1, bar: 5 }, { foo: 5, bar: 5 });
|
|
11
|
+
});
|
|
12
|
+
it("encodes correctly with read offset", () => {
|
|
13
|
+
const offset = st.createRememberedValue();
|
|
14
|
+
const spec = st.object({
|
|
15
|
+
foo: st.u32(),
|
|
16
|
+
offset: offset.save(st.s16()),
|
|
17
|
+
bar: st.pipe(st.u32(), st.offset("relative", offset.load())),
|
|
18
|
+
});
|
|
19
|
+
encodeTest(spec, { foo: 0, offset: -6, bar: 5 }, { foo: 5, offset: -6, bar: 5 });
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
describe("st.offset(absolute)", () => {
|
|
23
|
+
it("encodes correctly with fixed offset", () => {
|
|
24
|
+
const spec = st.object({
|
|
25
|
+
foo: st.u32(),
|
|
26
|
+
bar: st.pipe(st.u32(), st.offset("absolute", 0)),
|
|
27
|
+
});
|
|
28
|
+
encodeTest(spec, { foo: 0, bar: 5 }, { foo: 5, bar: 5 });
|
|
29
|
+
});
|
|
30
|
+
it("encodes correctly with read offset", () => {
|
|
31
|
+
const offset = st.createRememberedValue();
|
|
32
|
+
const spec = st.object({
|
|
33
|
+
foo: st.u32(),
|
|
34
|
+
offset: offset.save(st.u32()),
|
|
35
|
+
bar: st.pipe(st.u32(), st.offset("absolute", offset.load())),
|
|
36
|
+
});
|
|
37
|
+
encodeTest(spec, { foo: 0, offset: 0, bar: 5 }, { foo: 5, offset: 0, bar: 5 });
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Serializer } from "../types";
|
|
2
|
+
export type Transform<T = any, TNext = T> = (type: Serializer<TNext>) => Serializer<T>;
|
|
3
|
+
export declare function pipe<TStart, T1>(type: Serializer<TStart>, p1: Transform<T1, TStart>): Serializer<T1>;
|
|
4
|
+
export declare function pipe<TStart, T1, T2>(type: Serializer<TStart>, p1: Transform<T1, TStart>, p2: Transform<T2, T1>): Serializer<T2>;
|
|
5
|
+
export declare function pipe<TStart, T1, T2, T3>(type: Serializer<TStart>, p1: Transform<T1, TStart>, p2: Transform<T2, T1>, p3: Transform<T3, T2>): Serializer<T3>;
|
|
6
|
+
export declare function pipe<TStart, T1, T2, T3, T4>(type: Serializer<TStart>, p1: Transform<T1, TStart>, p2: Transform<T2, T1>, p3: Transform<T3, T2>, p4: Transform<T4, T3>): Serializer<T4>;
|
|
7
|
+
export declare function pipe<TStart, T1, T2, T3, T4, T5>(type: Serializer<TStart>, p1: Transform<T1, TStart>, p2: Transform<T2, T1>, p3: Transform<T3, T2>, p4: Transform<T4, T3>, p5: Transform<T5, T4>): Serializer<T5>;
|
|
8
|
+
export declare function pipe<TStart, T1, T2, T3, T4, T5, T6>(type: Serializer<TStart>, p1: Transform<T1, TStart>, p2: Transform<T2, T1>, p3: Transform<T3, T2>, p4: Transform<T4, T3>, p5: Transform<T5, T6>): Serializer<T6>;
|
|
9
|
+
export declare function pipe<TStart, T1, T2, T3, T4, T5, T6, T7>(type: Serializer<TStart>, p1: Transform<T1, TStart>, p2: Transform<T2, T1>, p3: Transform<T3, T2>, p4: Transform<T4, T3>, p5: Transform<T5, T6>, p6: Transform<T6, T7>): Serializer<T7>;
|
|
10
|
+
export declare function pipe<TStart, T1, T2, T3, T4, T5, T6, T7, T8>(type: Serializer<TStart>, p1: Transform<T1, TStart>, p2: Transform<T2, T1>, p3: Transform<T3, T2>, p4: Transform<T4, T3>, p5: Transform<T5, T6>, p6: Transform<T6, T7>, p7: Transform<T7, T8>): Serializer<T8>;
|
|
11
|
+
export declare function pipe<TStart, T1, T2, T3, T4, T5, T6, T7, T8, T9>(type: Serializer<TStart>, p1: Transform<T1, TStart>, p2: Transform<T2, T1>, p3: Transform<T3, T2>, p4: Transform<T4, T3>, p5: Transform<T5, T6>, p6: Transform<T6, T7>, p7: Transform<T7, T8>, p8: Transform<T8, T9>): Serializer<T9>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipe lets you chain modification functions together
|
|
3
|
+
* ```
|
|
4
|
+
* st.object({
|
|
5
|
+
* age: st.u32(),
|
|
6
|
+
* ageInMonths: st.pipe(
|
|
7
|
+
* st.u32(),
|
|
8
|
+
* st.modify(v => v * 12),
|
|
9
|
+
* st.offset("relative", -8),
|
|
10
|
+
* )})
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export function pipe(type, ...pipeline) {
|
|
14
|
+
//@ts-ignore
|
|
15
|
+
return pipeline.reduce((v, func) => func(v), type);
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function toAscii(): import("./pipe").Transform<string, ArrayBuffer>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { encode } from "./encode";
|
|
2
|
+
export function toAscii() {
|
|
3
|
+
return encode({
|
|
4
|
+
encode: (v) => new Uint8Array(Array.from(v).map((char) => validateAscii(char.charCodeAt(0)))).buffer,
|
|
5
|
+
decode: (v) => Array.from(new Uint8Array(v))
|
|
6
|
+
.map((v) => String.fromCharCode(v))
|
|
7
|
+
.join(""),
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
const validateAscii = (v) => {
|
|
11
|
+
if (v >= 0 && v <= 127)
|
|
12
|
+
return v;
|
|
13
|
+
throw new Error(`Invalid value: ${v}`);
|
|
14
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it } from "bun:test";
|
|
2
|
+
import { encodeTest, encodeSnapshotTest, encodeFailTest } from "../utils.test";
|
|
3
|
+
import * as st from "../index";
|
|
4
|
+
describe("st.toAscii", () => {
|
|
5
|
+
it("encode correctly", () => {
|
|
6
|
+
encodeTest(st.pipe(st.bytes(5), st.toAscii()), //
|
|
7
|
+
"a6534");
|
|
8
|
+
encodeTest(st.pipe(st.bytes(3), st.toAscii()), //
|
|
9
|
+
"-as");
|
|
10
|
+
});
|
|
11
|
+
it("invalid length throws error", () => {
|
|
12
|
+
encodeFailTest(st.pipe(st.bytes(4), st.toAscii()), //
|
|
13
|
+
"asd");
|
|
14
|
+
});
|
|
15
|
+
it("invalid character throws error", () => {
|
|
16
|
+
encodeFailTest(st.pipe(st.bytes(4), st.toAscii()), //
|
|
17
|
+
"asdé");
|
|
18
|
+
});
|
|
19
|
+
it("snapshots", () => {
|
|
20
|
+
encodeSnapshotTest(st.pipe(st.bytes(4), st.toAscii()), //
|
|
21
|
+
"634f");
|
|
22
|
+
encodeSnapshotTest(st.pipe(st.bytes(6), st.toAscii()), //
|
|
23
|
+
"hdjdp_");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function toBase64(): import("./pipe").Transform<string, ArrayBuffer>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, it } from "bun:test";
|
|
2
|
+
import { encodeTest, encodeSnapshotTest, encodeFailTest } from "../utils.test";
|
|
3
|
+
import * as st from "../index";
|
|
4
|
+
describe("st.toBase64", () => {
|
|
5
|
+
it("encode correctly", () => {
|
|
6
|
+
encodeTest(st.pipe(st.bytes(1), st.toBase64()), //
|
|
7
|
+
"rw==");
|
|
8
|
+
encodeTest(st.pipe(st.bytes(2), st.toBase64()), //
|
|
9
|
+
"rx8=");
|
|
10
|
+
encodeTest(st.pipe(st.bytes(3), st.toBase64()), //
|
|
11
|
+
"rx9K");
|
|
12
|
+
});
|
|
13
|
+
it("invalid character error", () => {
|
|
14
|
+
encodeFailTest(st.pipe(st.bytes(3), st.toBase64()), //
|
|
15
|
+
"rx9-");
|
|
16
|
+
});
|
|
17
|
+
it("invalid length error", () => {
|
|
18
|
+
encodeFailTest(st.pipe(st.bytes(4), st.toBase64()), //
|
|
19
|
+
"rx9L");
|
|
20
|
+
});
|
|
21
|
+
it("snapshots", () => {
|
|
22
|
+
encodeSnapshotTest(st.pipe(st.bytes(3), st.toBase64()), //
|
|
23
|
+
"rx9K");
|
|
24
|
+
encodeSnapshotTest(st.pipe(st.bytes(6), st.toBase64()), //
|
|
25
|
+
"fR9KIj8V");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts an ArrayBuffer to the byte digits
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* st.pipe(
|
|
6
|
+
* st.bytes(3),
|
|
7
|
+
* st.toBytes()
|
|
8
|
+
* )
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* `ArrayBuffer([0, 255, c])` => `[0, 255, ArrayBuffer([0, 255, 0])]`
|
|
12
|
+
*/
|
|
13
|
+
export declare function toBytes(): import("./pipe").Transform<number[], ArrayBuffer>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { encode } from "./encode";
|
|
2
|
+
/**
|
|
3
|
+
* Converts an ArrayBuffer to the byte digits
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* st.pipe(
|
|
7
|
+
* st.bytes(3),
|
|
8
|
+
* st.toBytes()
|
|
9
|
+
* )
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* `ArrayBuffer([0, 255, c])` => `[0, 255, ArrayBuffer([0, 255, 0])]`
|
|
13
|
+
*/
|
|
14
|
+
export function toBytes() {
|
|
15
|
+
return encode({
|
|
16
|
+
encode: (v) => new Uint8Array(v.map(validateByte)).buffer,
|
|
17
|
+
decode: (v) => Array.from(new Uint8Array(v)),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const validateByte = (v) => {
|
|
21
|
+
if (v >= 0 && v <= 255)
|
|
22
|
+
return v;
|
|
23
|
+
throw new Error(`Invalid value: ${v}`);
|
|
24
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, it } from "bun:test";
|
|
2
|
+
import { encodeTest, encodeSnapshotTest, encodeFailTest } from "../utils.test";
|
|
3
|
+
import * as st from "../index";
|
|
4
|
+
describe("st.toBytes", () => {
|
|
5
|
+
it("encode correctly", () => {
|
|
6
|
+
encodeTest(st.pipe(st.bytes(3), st.toBytes()), //
|
|
7
|
+
[0, 1, 2]);
|
|
8
|
+
encodeTest(st.pipe(st.bytes(3), st.toBytes()), //
|
|
9
|
+
[255, 0, 127]);
|
|
10
|
+
});
|
|
11
|
+
it("invalid length throws error", () => {
|
|
12
|
+
encodeFailTest(st.pipe(st.bytes(3), st.toBytes()), //
|
|
13
|
+
[0, 1]);
|
|
14
|
+
});
|
|
15
|
+
it("invalid values throws error", () => {
|
|
16
|
+
encodeFailTest(st.pipe(st.bytes(3), st.toBytes()), //
|
|
17
|
+
[256, 1, 1]);
|
|
18
|
+
encodeFailTest(st.pipe(st.bytes(3), st.toBytes()), //
|
|
19
|
+
[-1, 1, 1]);
|
|
20
|
+
});
|
|
21
|
+
it("snapshots", () => {
|
|
22
|
+
encodeSnapshotTest(st.pipe(st.bytes(3), st.toBytes()), //
|
|
23
|
+
[73, 12, 3]);
|
|
24
|
+
encodeSnapshotTest(st.pipe(st.bytes(6), st.toBytes()), //
|
|
25
|
+
[34, 10, 0, 74, 31, 23]);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts an ArrayBuffer to uppercase hex
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* st.pipe(
|
|
6
|
+
* st.bytes(3),
|
|
7
|
+
* st.toHex()
|
|
8
|
+
* )
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* `ArrayBuffer([0, 255, 0])` => `00FF00`
|
|
12
|
+
*/
|
|
13
|
+
export declare function toHex(): import("./pipe").Transform<string, ArrayBuffer>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { encode } from "./encode";
|
|
2
|
+
/**
|
|
3
|
+
* Converts an ArrayBuffer to uppercase hex
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* st.pipe(
|
|
7
|
+
* st.bytes(3),
|
|
8
|
+
* st.toHex()
|
|
9
|
+
* )
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* `ArrayBuffer([0, 255, 0])` => `00FF00`
|
|
13
|
+
*/
|
|
14
|
+
export function toHex() {
|
|
15
|
+
return encode({
|
|
16
|
+
encode: (v) => Uint8Array.fromHex(v).buffer,
|
|
17
|
+
decode: (v) => new Uint8Array(v).toHex().toUpperCase(),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it } from "bun:test";
|
|
2
|
+
import { encodeTest, encodeSnapshotTest, encodeFailTest } from "../utils.test";
|
|
3
|
+
import * as st from "../index";
|
|
4
|
+
describe("st.toHex", () => {
|
|
5
|
+
it("encode correctly", () => {
|
|
6
|
+
encodeTest(st.pipe(st.bytes(3), st.toHex()), //
|
|
7
|
+
"FFAB01");
|
|
8
|
+
});
|
|
9
|
+
it("invalid digits error", () => {
|
|
10
|
+
encodeFailTest(st.pipe(st.bytes(3), st.toHex()), //
|
|
11
|
+
"00000G");
|
|
12
|
+
});
|
|
13
|
+
it("invalid length error", () => {
|
|
14
|
+
encodeFailTest(st.pipe(st.bytes(3), st.toHex()), //
|
|
15
|
+
"0000");
|
|
16
|
+
});
|
|
17
|
+
it("snapshots", () => {
|
|
18
|
+
encodeSnapshotTest(st.pipe(st.bytes(3), st.toHex()), //
|
|
19
|
+
"FFAB01");
|
|
20
|
+
encodeSnapshotTest(st.pipe(st.bytes(5), st.toHex()), //
|
|
21
|
+
"0000000000");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type TypedArray = {
|
|
2
|
+
buffer: ArrayBuffer;
|
|
3
|
+
};
|
|
4
|
+
type TypedArrayConstructor<T extends TypedArray> = new (array: ArrayBuffer) => T;
|
|
5
|
+
/**
|
|
6
|
+
* Converts an `ArrayBuffer` to a `TypedArray`
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* st.pipe(
|
|
10
|
+
* st.bytes(16),
|
|
11
|
+
* toTypedArray(Uint8Array),
|
|
12
|
+
* )
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function toTypedArray<T extends TypedArray>(arrayType: TypedArrayConstructor<T>): import("./pipe").Transform<T, ArrayBuffer>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { encode } from "./encode";
|
|
2
|
+
/**
|
|
3
|
+
* Converts an `ArrayBuffer` to a `TypedArray`
|
|
4
|
+
*
|
|
5
|
+
* ```
|
|
6
|
+
* st.pipe(
|
|
7
|
+
* st.bytes(16),
|
|
8
|
+
* toTypedArray(Uint8Array),
|
|
9
|
+
* )
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export function toTypedArray(arrayType) {
|
|
13
|
+
return encode({
|
|
14
|
+
encode: (v) => v.buffer,
|
|
15
|
+
decode: (v) => new arrayType(v),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, it } from "bun:test";
|
|
2
|
+
import { encodeTest, encodeSnapshotTest, expectError } from "../utils.test";
|
|
3
|
+
import * as st from "../index";
|
|
4
|
+
describe("st.toArrayBuffer", () => {
|
|
5
|
+
it("encode correctly", () => {
|
|
6
|
+
encodeTest(st.pipe(st.bytes(6), //
|
|
7
|
+
st.toTypedArray(Uint8Array)), new Uint8Array([1, 2, 3, 4, 5, 6]));
|
|
8
|
+
});
|
|
9
|
+
it("encode different types correctly", () => {
|
|
10
|
+
encodeTest(st.pipe(st.bytes(3), //
|
|
11
|
+
st.toTypedArray(Uint8Array)), new Uint8Array([1, 2, 3]));
|
|
12
|
+
encodeTest(st.pipe(st.bytes(5 * 2), //
|
|
13
|
+
st.toTypedArray(Uint16Array)), new Uint16Array([1, 2, 3, 63, 74]));
|
|
14
|
+
encodeTest(st.pipe(st.bytes(3 * 8), //
|
|
15
|
+
st.toTypedArray(Float64Array)), new Float64Array([Math.random(), Math.random(), Math.random()]));
|
|
16
|
+
});
|
|
17
|
+
it("errors on different types", () => {
|
|
18
|
+
expectError(() => {
|
|
19
|
+
st.write(st.pipe(st.bytes(3), //
|
|
20
|
+
st.toTypedArray(Uint16Array)), new Uint16Array([1]));
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
it("snapshot tests", () => {
|
|
24
|
+
encodeSnapshotTest(st.pipe(st.bytes(4 * 2), //
|
|
25
|
+
st.toTypedArray(Uint16Array)), new Uint16Array([1, 6, 3, 12]));
|
|
26
|
+
encodeSnapshotTest(st.pipe(st.bytes(3 * 8), //
|
|
27
|
+
st.toTypedArray(Float64Array)), new Float64Array([1.52, 0.3523, 0.152]));
|
|
28
|
+
});
|
|
29
|
+
});
|
package/dist/types.d.ts
CHANGED
|
@@ -3,13 +3,16 @@ export type Serializer<TIn, TOut = TIn> = {
|
|
|
3
3
|
write: (ctx: WriterContext, value: TIn) => void;
|
|
4
4
|
read: (ctx: ReaderContext) => TOut;
|
|
5
5
|
};
|
|
6
|
+
export type SerializationContext = WriterContext | ReaderContext;
|
|
6
7
|
export interface WriterContext {
|
|
8
|
+
stack: string[];
|
|
7
9
|
buffer: ArrayBuffer;
|
|
8
10
|
view: DataView;
|
|
9
11
|
offset: number;
|
|
10
12
|
alloc: (length: number) => void;
|
|
11
13
|
}
|
|
12
14
|
export interface ReaderContext {
|
|
15
|
+
stack: string[];
|
|
13
16
|
buffer: ArrayBuffer;
|
|
14
17
|
view: DataView;
|
|
15
18
|
offset: number;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function lazy(type) {
|
|
2
|
+
let _size;
|
|
3
|
+
let resolve = () => {
|
|
4
|
+
resolve = () => { };
|
|
5
|
+
const { size, write, read } = type();
|
|
6
|
+
_size = size ?? undefined;
|
|
7
|
+
serializer.read = read;
|
|
8
|
+
serializer.write = write;
|
|
9
|
+
};
|
|
10
|
+
const serializer = {
|
|
11
|
+
get size() {
|
|
12
|
+
resolve();
|
|
13
|
+
return _size;
|
|
14
|
+
},
|
|
15
|
+
read(ctx) {
|
|
16
|
+
resolve();
|
|
17
|
+
return this.read(ctx);
|
|
18
|
+
},
|
|
19
|
+
write(ctx, value) {
|
|
20
|
+
resolve();
|
|
21
|
+
return this.write(ctx, value);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
return serializer;
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { encodeTest } from "../utils.test";
|
|
3
|
+
import * as st from "../index";
|
|
4
|
+
describe("st.lazy", () => {
|
|
5
|
+
it("encodes correctly", () => {
|
|
6
|
+
const spec = st.lazy(() => st.object({
|
|
7
|
+
a: st.array(4, st.u8()),
|
|
8
|
+
b: st.u16(),
|
|
9
|
+
}));
|
|
10
|
+
encodeTest(spec, {
|
|
11
|
+
a: [1, 2, 3, 4],
|
|
12
|
+
b: 2,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
it("nested objects function", () => {
|
|
16
|
+
const spec = st.object({
|
|
17
|
+
a: st.array(4, st.u8()),
|
|
18
|
+
b: st.lazy(() => st.object({
|
|
19
|
+
a: st.lazy(() => st.u16()),
|
|
20
|
+
})),
|
|
21
|
+
});
|
|
22
|
+
encodeTest(spec, {
|
|
23
|
+
a: [1, 2, 3, 4],
|
|
24
|
+
b: { a: 1 },
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
it("size is calculated correctly", () => {
|
|
28
|
+
const spec = st.lazy(() => st.object({
|
|
29
|
+
b: st.lazy(() => st.object({
|
|
30
|
+
a: st.u32(),
|
|
31
|
+
b: st.u8(),
|
|
32
|
+
})),
|
|
33
|
+
}));
|
|
34
|
+
expect(spec.size).toBe(5);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Serializer } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* createReference lets you defer writing a value until later, useful for lengths and sizes
|
|
4
|
+
*
|
|
5
|
+
* ---
|
|
6
|
+
* ```
|
|
7
|
+
* const length = st.createReference<number>()
|
|
8
|
+
* st.object({
|
|
9
|
+
* length: length.pointer(st.u32()),
|
|
10
|
+
* type: st.u8(),
|
|
11
|
+
* data: st.sizedBytes(length.deref())
|
|
12
|
+
* })
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function createReference<T>(): {
|
|
16
|
+
pointer: (serializer: Serializer<T>) => Serializer<T>;
|
|
17
|
+
deref: () => Serializer<T>;
|
|
18
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createReference lets you defer writing a value until later, useful for lengths and sizes
|
|
3
|
+
*
|
|
4
|
+
* ---
|
|
5
|
+
* ```
|
|
6
|
+
* const length = st.createReference<number>()
|
|
7
|
+
* st.object({
|
|
8
|
+
* length: length.pointer(st.u32()),
|
|
9
|
+
* type: st.u8(),
|
|
10
|
+
* data: st.sizedBytes(length.deref())
|
|
11
|
+
* })
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function createReference() {
|
|
15
|
+
const readStack = [];
|
|
16
|
+
const writeStack = [];
|
|
17
|
+
/**
|
|
18
|
+
* When reading: reads the value normal and saves it for later
|
|
19
|
+
*
|
|
20
|
+
* When writing: omits this value, it is written by deref
|
|
21
|
+
*
|
|
22
|
+
* ---
|
|
23
|
+
* ```
|
|
24
|
+
* const length = st.createReference<number>()
|
|
25
|
+
* st.object({
|
|
26
|
+
* length: length.pointer(st.u32()),
|
|
27
|
+
* type: st.u8(),
|
|
28
|
+
* data: st.sizedBytes(length.deref())
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function pointer(serializer) {
|
|
33
|
+
const size = serializer.size;
|
|
34
|
+
if (size === undefined)
|
|
35
|
+
throw new Error("Serializer must be sized");
|
|
36
|
+
return {
|
|
37
|
+
size,
|
|
38
|
+
read: (ctx) => {
|
|
39
|
+
const value = serializer.read(ctx);
|
|
40
|
+
readStack.push(value);
|
|
41
|
+
return value;
|
|
42
|
+
},
|
|
43
|
+
write: (ctx, value) => {
|
|
44
|
+
writeStack.push({
|
|
45
|
+
offset: ctx.offset,
|
|
46
|
+
serializer,
|
|
47
|
+
});
|
|
48
|
+
ctx.offset += size;
|
|
49
|
+
return value;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* When reading: gets the value stored in pointer
|
|
55
|
+
*
|
|
56
|
+
* When writing: saves the value into the pointer
|
|
57
|
+
*
|
|
58
|
+
* By default the value is removed from the pointer,
|
|
59
|
+
* but you can retain it to ensure the next value is
|
|
60
|
+
*
|
|
61
|
+
* ---
|
|
62
|
+
* ```
|
|
63
|
+
* const length = st.createReference<number>()
|
|
64
|
+
* st.object({
|
|
65
|
+
* length: length.pointer(st.u32()),
|
|
66
|
+
* type: st.u8(),
|
|
67
|
+
* data: st.sizedBytes(length.deref())
|
|
68
|
+
* })
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
function deref() {
|
|
72
|
+
return {
|
|
73
|
+
size: 0,
|
|
74
|
+
read: () => {
|
|
75
|
+
if (readStack.length === 0)
|
|
76
|
+
throw new Error("Read Stack Empty");
|
|
77
|
+
return readStack.pop();
|
|
78
|
+
},
|
|
79
|
+
write: (ctx, value) => {
|
|
80
|
+
if (writeStack.length === 0)
|
|
81
|
+
throw new Error("Write Stack is emtpy");
|
|
82
|
+
const { offset, serializer } = writeStack.pop();
|
|
83
|
+
const start = ctx.offset;
|
|
84
|
+
ctx.offset = offset;
|
|
85
|
+
serializer.write(ctx, value);
|
|
86
|
+
ctx.offset = start;
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return { pointer, deref };
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { encodeTest, encodeSnapshotTest, expectError } from "../utils.test";
|
|
3
|
+
import * as st from "../index";
|
|
4
|
+
describe("st.createReference", () => {
|
|
5
|
+
it("encodes correctly", () => {
|
|
6
|
+
const length = st.createReference();
|
|
7
|
+
const spec = st.object({
|
|
8
|
+
length: length.pointer(st.u32()),
|
|
9
|
+
numbers: st.list(length.deref(), st.u8()),
|
|
10
|
+
});
|
|
11
|
+
expect(spec.size).toBe(undefined);
|
|
12
|
+
encodeTest(spec, //
|
|
13
|
+
{ length: 0, numbers: [1, 3, 4] }, { length: 3, numbers: [1, 3, 4] });
|
|
14
|
+
});
|
|
15
|
+
it("cant create reference to pointer", () => {
|
|
16
|
+
const length = st.createReference();
|
|
17
|
+
expectError(() => {
|
|
18
|
+
length.pointer(st.sizedBytes(st.u8()));
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
it("multiple runs function", () => {
|
|
22
|
+
const v = st.createReference();
|
|
23
|
+
const spec = st.object({
|
|
24
|
+
a: v.pointer(st.u32()),
|
|
25
|
+
b: st.list(v.deref(), st.u8()),
|
|
26
|
+
});
|
|
27
|
+
for (let i = 0; i < 3; i++) {
|
|
28
|
+
encodeTest(spec, //
|
|
29
|
+
{ a: NaN, b: [1, 3, 4] }, { a: 3, b: [1, 3, 4] });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
it("handles recursive objects", () => {
|
|
33
|
+
const size = st.createReference();
|
|
34
|
+
//@ts-expect-error, recursive types
|
|
35
|
+
const Node = st.lazy(() => st.object({
|
|
36
|
+
size: size.pointer(st.u32()),
|
|
37
|
+
children: st.list(size.deref(), Node),
|
|
38
|
+
}));
|
|
39
|
+
encodeTest(Node, {
|
|
40
|
+
size: NaN,
|
|
41
|
+
children: [
|
|
42
|
+
{ size: NaN, children: [] },
|
|
43
|
+
{ size: NaN, children: [{ size: NaN, children: [] }] },
|
|
44
|
+
],
|
|
45
|
+
}, {
|
|
46
|
+
size: 2,
|
|
47
|
+
children: [
|
|
48
|
+
{ size: 0, children: [] },
|
|
49
|
+
{ size: 1, children: [{ size: 0, children: [] }] },
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
it("snapshots are correct", () => {
|
|
54
|
+
const v = st.createReference();
|
|
55
|
+
encodeSnapshotTest(st.object({
|
|
56
|
+
a: v.pointer(st.u32()),
|
|
57
|
+
b: st.list(v.deref(), st.u8()),
|
|
58
|
+
}), {
|
|
59
|
+
a: 3,
|
|
60
|
+
b: [1, 3, 4],
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|