@nnilky/structo 1.0.7 → 1.0.9

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.
Files changed (91) hide show
  1. package/README.md +3 -3
  2. package/dist/datatypes/containers/array.d.ts +1 -1
  3. package/dist/datatypes/containers/array.js +9 -0
  4. package/dist/datatypes/containers/array.test.js +2 -2
  5. package/dist/datatypes/containers/exhuastiveArray.d.ts +2 -2
  6. package/dist/datatypes/containers/exhuastiveArray.js +13 -2
  7. package/dist/datatypes/containers/exhuastiveArray.test.js +2 -2
  8. package/dist/datatypes/containers/fastObject.d.ts +3 -3
  9. package/dist/datatypes/containers/fastObject.js +2 -2
  10. package/dist/datatypes/containers/fastObject.test.js +2 -2
  11. package/dist/datatypes/containers/list.d.ts +1 -1
  12. package/dist/datatypes/containers/list.js +14 -2
  13. package/dist/datatypes/containers/list.test.js +2 -2
  14. package/dist/datatypes/containers/object.d.ts +1 -1
  15. package/dist/datatypes/containers/object.js +8 -8
  16. package/dist/datatypes/containers/object.test.js +2 -2
  17. package/dist/datatypes/containers/taggedUnion.d.ts +1 -1
  18. package/dist/datatypes/containers/taggedUnion.test.js +2 -2
  19. package/dist/datatypes/index.d.ts +13 -13
  20. package/dist/datatypes/index.js +13 -13
  21. package/dist/datatypes/numbers/bigints.d.ts +1 -1
  22. package/dist/datatypes/numbers/bigints.test.js +2 -2
  23. package/dist/datatypes/numbers/floats.d.ts +1 -1
  24. package/dist/datatypes/numbers/floats.test.js +2 -2
  25. package/dist/datatypes/numbers/sints.d.ts +1 -1
  26. package/dist/datatypes/numbers/sints.test.js +2 -2
  27. package/dist/datatypes/numbers/uints.d.ts +1 -1
  28. package/dist/datatypes/numbers/uints.test.js +2 -2
  29. package/dist/datatypes/values/bytes.d.ts +1 -1
  30. package/dist/datatypes/values/bytes.test.js +2 -2
  31. package/dist/datatypes/values/sizedbytes.d.ts +1 -1
  32. package/dist/datatypes/values/sizedbytes.test.js +2 -2
  33. package/dist/datatypes/values/string.d.ts +1 -1
  34. package/dist/datatypes/values/string.test.js +5 -2
  35. package/dist/index.d.ts +6 -6
  36. package/dist/index.js +5 -5
  37. package/dist/read.d.ts +2 -6
  38. package/dist/read.js +9 -1
  39. package/dist/transforms/encode.d.ts +5 -2
  40. package/dist/transforms/encode.js +2 -1
  41. package/dist/transforms/enum.d.ts +1 -1
  42. package/dist/transforms/enum.js +12 -9
  43. package/dist/transforms/enum.test.js +2 -2
  44. package/dist/transforms/index.d.ts +11 -12
  45. package/dist/transforms/index.js +11 -12
  46. package/dist/transforms/literal.d.ts +1 -1
  47. package/dist/transforms/literal.js +12 -9
  48. package/dist/transforms/literal.test.js +2 -2
  49. package/dist/transforms/modify.d.ts +20 -2
  50. package/dist/transforms/modify.js +21 -1
  51. package/dist/transforms/modify.test.d.ts +1 -0
  52. package/dist/transforms/modify.test.js +17 -0
  53. package/dist/transforms/offset.d.ts +2 -0
  54. package/dist/transforms/offset.js +20 -0
  55. package/dist/transforms/offset.test.d.ts +1 -0
  56. package/dist/transforms/offset.test.js +39 -0
  57. package/dist/transforms/pipe.d.ts +1 -1
  58. package/dist/transforms/pipe.js +1 -1
  59. package/dist/transforms/toAscii.d.ts +1 -1
  60. package/dist/transforms/toAscii.js +7 -4
  61. package/dist/transforms/toAscii.test.js +2 -2
  62. package/dist/transforms/toBase64.d.ts +1 -1
  63. package/dist/transforms/toBase64.js +5 -2
  64. package/dist/transforms/toBase64.test.js +2 -2
  65. package/dist/transforms/toBytes.d.ts +1 -1
  66. package/dist/transforms/toBytes.js +5 -2
  67. package/dist/transforms/toBytes.test.js +2 -2
  68. package/dist/transforms/toHex.d.ts +1 -1
  69. package/dist/transforms/toHex.js +5 -2
  70. package/dist/transforms/toHex.test.js +2 -2
  71. package/dist/transforms/toTypedArray.d.ts +1 -1
  72. package/dist/transforms/toTypedArray.js +5 -2
  73. package/dist/transforms/toTypedArray.test.js +2 -2
  74. package/dist/types.d.ts +3 -0
  75. package/dist/utilities/index.d.ts +3 -2
  76. package/dist/utilities/index.js +3 -2
  77. package/dist/utilities/lazy.d.ts +1 -1
  78. package/dist/utilities/lazy.js +4 -4
  79. package/dist/utilities/lazy.test.js +2 -2
  80. package/dist/utilities/reference.d.ts +18 -0
  81. package/dist/utilities/reference.js +91 -0
  82. package/dist/utilities/reference.test.d.ts +1 -0
  83. package/dist/utilities/reference.test.js +63 -0
  84. package/dist/utilities/remember.d.ts +7 -6
  85. package/dist/utilities/remember.js +45 -8
  86. package/dist/utilities/remember.test.js +3 -7
  87. package/dist/utils.test.d.ts +1 -1
  88. package/dist/utils.test.js +1 -1
  89. package/dist/write.d.ts +1 -1
  90. package/dist/write.js +9 -1
  91. package/package.json +9 -9
@@ -1,12 +1,11 @@
1
- export { pipe } from "./pipe";
2
- export { encode } from "./encode";
3
- export { modify } from "./modify";
4
- export { literal } from "./literal";
5
- export { enum } from "./enum";
6
- export { fixedOffset } from "./fixedOffset";
7
- export { noAdvance } from "./noAdvance";
8
- export { toAscii } from "./toAscii";
9
- export { toBytes } from "./toBytes";
10
- export { toHex } from "./toHex";
11
- export { toBase64 } from "./toBase64";
12
- export { toTypedArray } from "./toTypedArray";
1
+ export { pipe } from "./pipe.js";
2
+ export { encode } from "./encode.js";
3
+ export { modify } from "./modify.js";
4
+ export { literal } from "./literal.js";
5
+ export { enum } from "./enum.js";
6
+ export { offset } from "./offset.js";
7
+ export { toAscii } from "./toAscii.js";
8
+ export { toBytes } from "./toBytes.js";
9
+ export { toHex } from "./toHex.js";
10
+ export { toBase64 } from "./toBase64.js";
11
+ export { toTypedArray } from "./toTypedArray.js";
@@ -1 +1 @@
1
- export declare function literal<const T>(value: T): import("./pipe").Transform<T, any>;
1
+ export declare function literal<const T>(value: T): import("./pipe.js").Transform<T, any>;
@@ -1,12 +1,15 @@
1
- import { encode } from "./encode";
1
+ import { encode } from "./encode.js";
2
2
  export function literal(value) {
3
- return encode((v) => {
4
- if (v !== value)
5
- throw new Error(`Invalid literal variant: ${v}`);
6
- return v;
7
- }, (v) => {
8
- if (v !== value)
9
- throw new Error(`Invalid literal variant: ${v}`);
10
- return v;
3
+ return encode({
4
+ encode: (v) => {
5
+ if (v !== value)
6
+ throw new Error(`Invalid literal variant: ${v}`);
7
+ return v;
8
+ },
9
+ decode: (v) => {
10
+ if (v !== value)
11
+ throw new Error(`Invalid literal variant: ${v}`);
12
+ return v;
13
+ },
11
14
  });
12
15
  }
@@ -1,6 +1,6 @@
1
1
  import { describe, it } from "node:test";
2
- import * as st from "../index";
3
- import { encodeTest, encodeFailTest } from "../utils.test";
2
+ import * as st from "../index.js";
3
+ import { encodeTest, encodeFailTest } from "../utils.test.js";
4
4
  describe("st.literal", () => {
5
5
  it("encode correctly", () => {
6
6
  encodeTest(st.pipe(st.u8(), st.literal(0)), //
@@ -1,2 +1,20 @@
1
- import type { Transform } from "./pipe";
2
- export declare function modify<T>(callback: (value: T) => T): Transform<T>;
1
+ import type { Transform } from "./pipe.js";
2
+ /**
3
+ * A readonly modification function, useful for sizes or offsets.
4
+ *
5
+ * If you want a writable value, use `st.encode` instead
6
+ *
7
+ *
8
+ * ```ts
9
+ * length: length.save(st.u32())
10
+ * st.sizedBytes(
11
+ * st.pipe(
12
+ * length.load(),
13
+ * st.modify(v => v - 8),
14
+ * )
15
+ * ))
16
+ *
17
+ * st.pipe()
18
+ * ```
19
+ */
20
+ export declare function modify<TIn, TOut>(callback: (value: TIn) => TOut): Transform<TOut, TIn>;
@@ -1,7 +1,27 @@
1
+ /**
2
+ * A readonly modification function, useful for sizes or offsets.
3
+ *
4
+ * If you want a writable value, use `st.encode` instead
5
+ *
6
+ *
7
+ * ```ts
8
+ * length: length.save(st.u32())
9
+ * st.sizedBytes(
10
+ * st.pipe(
11
+ * length.load(),
12
+ * st.modify(v => v - 8),
13
+ * )
14
+ * ))
15
+ *
16
+ * st.pipe()
17
+ * ```
18
+ */
1
19
  export function modify(callback) {
2
20
  return (type) => ({
3
21
  size: type.size,
4
22
  read: (ctx) => callback(type.read(ctx)),
5
- write: (ctx, value) => type.write(ctx, callback(value)),
23
+ write: () => {
24
+ throw new Error("Cannot write an encoded value");
25
+ },
6
26
  });
7
27
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { encodeFailTest } from "../utils.test.js";
3
+ import * as st from "../index.js";
4
+ describe("st.modify", () => {
5
+ it("encodes correctly", () => {
6
+ const data = st.write(st.u32(), 10);
7
+ const output = st.read(st.pipe(st.u32(), st.modify((v) => v + 8)), data);
8
+ expect(output).toBe(18);
9
+ });
10
+ it("cannot be written", () => {
11
+ encodeFailTest(st.pipe(st.u32(), st.modify((v) => v + 8)), 3);
12
+ });
13
+ it("does not modify size", () => {
14
+ const spec = st.pipe(st.u32(), st.modify((v) => v + 8));
15
+ expect(spec.size).toBe(4);
16
+ });
17
+ });
@@ -0,0 +1,2 @@
1
+ import type { Serializer } from "../types.js";
2
+ export declare function offset<TIn, TOut>(behaviour: "relative" | "absolute", offset: Serializer<number> | number): (type: Serializer<TIn, TOut>) => Serializer<TIn, TOut>;
@@ -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.js";
3
+ import * as st from "../index.js";
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
+ });
@@ -1,4 +1,4 @@
1
- import type { Serializer } from "../types";
1
+ import type { Serializer } from "../types.js";
2
2
  export type Transform<T = any, TNext = T> = (type: Serializer<TNext>) => Serializer<T>;
3
3
  export declare function pipe<TStart, T1>(type: Serializer<TStart>, p1: Transform<T1, TStart>): Serializer<T1>;
4
4
  export declare function pipe<TStart, T1, T2>(type: Serializer<TStart>, p1: Transform<T1, TStart>, p2: Transform<T2, T1>): Serializer<T2>;
@@ -6,7 +6,7 @@
6
6
  * ageInMonths: st.pipe(
7
7
  * st.u32(),
8
8
  * st.modify(v => v * 12),
9
- * st.offset(-8),
9
+ * st.offset("relative", -8),
10
10
  * )})
11
11
  * ```
12
12
  */
@@ -1 +1 @@
1
- export declare function toAscii(): import("./pipe").Transform<string, ArrayBuffer>;
1
+ export declare function toAscii(): import("./pipe.js").Transform<string, ArrayBuffer>;
@@ -1,8 +1,11 @@
1
- import { encode } from "./encode";
1
+ import { encode } from "./encode.js";
2
2
  export function toAscii() {
3
- return encode((v) => new Uint8Array(Array.from(v).map((char) => validateAscii(char.charCodeAt(0)))).buffer, (v) => Array.from(new Uint8Array(v))
4
- .map((v) => String.fromCharCode(v))
5
- .join(""));
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
+ });
6
9
  }
7
10
  const validateAscii = (v) => {
8
11
  if (v >= 0 && v <= 127)
@@ -1,6 +1,6 @@
1
1
  import { describe, it } from "bun:test";
2
- import { encodeTest, encodeSnapshotTest, encodeFailTest } from "../utils.test";
3
- import * as st from "../index";
2
+ import { encodeTest, encodeSnapshotTest, encodeFailTest } from "../utils.test.js";
3
+ import * as st from "../index.js";
4
4
  describe("st.toAscii", () => {
5
5
  it("encode correctly", () => {
6
6
  encodeTest(st.pipe(st.bytes(5), st.toAscii()), //
@@ -1 +1 @@
1
- export declare function toBase64(): import("./pipe").Transform<string, ArrayBuffer>;
1
+ export declare function toBase64(): import("./pipe.js").Transform<string, ArrayBuffer>;
@@ -1,4 +1,7 @@
1
- import { encode } from "./encode";
1
+ import { encode } from "./encode.js";
2
2
  export function toBase64() {
3
- return encode((v) => Uint8Array.fromBase64(v).buffer, (v) => new Uint8Array(v).toBase64());
3
+ return encode({
4
+ encode: (v) => Uint8Array.fromBase64(v).buffer,
5
+ decode: (v) => new Uint8Array(v).toBase64(),
6
+ });
4
7
  }
@@ -1,6 +1,6 @@
1
1
  import { describe, it } from "bun:test";
2
- import { encodeTest, encodeSnapshotTest, encodeFailTest } from "../utils.test";
3
- import * as st from "../index";
2
+ import { encodeTest, encodeSnapshotTest, encodeFailTest } from "../utils.test.js";
3
+ import * as st from "../index.js";
4
4
  describe("st.toBase64", () => {
5
5
  it("encode correctly", () => {
6
6
  encodeTest(st.pipe(st.bytes(1), st.toBase64()), //
@@ -10,4 +10,4 @@
10
10
  *
11
11
  * `ArrayBuffer([0, 255, c])` => `[0, 255, ArrayBuffer([0, 255, 0])]`
12
12
  */
13
- export declare function toBytes(): import("./pipe").Transform<number[], ArrayBuffer>;
13
+ export declare function toBytes(): import("./pipe.js").Transform<number[], ArrayBuffer>;
@@ -1,4 +1,4 @@
1
- import { encode } from "./encode";
1
+ import { encode } from "./encode.js";
2
2
  /**
3
3
  * Converts an ArrayBuffer to the byte digits
4
4
  *
@@ -12,7 +12,10 @@ import { encode } from "./encode";
12
12
  * `ArrayBuffer([0, 255, c])` => `[0, 255, ArrayBuffer([0, 255, 0])]`
13
13
  */
14
14
  export function toBytes() {
15
- return encode((v) => new Uint8Array(v.map(validateByte)).buffer, (v) => Array.from(new Uint8Array(v)));
15
+ return encode({
16
+ encode: (v) => new Uint8Array(v.map(validateByte)).buffer,
17
+ decode: (v) => Array.from(new Uint8Array(v)),
18
+ });
16
19
  }
17
20
  const validateByte = (v) => {
18
21
  if (v >= 0 && v <= 255)
@@ -1,6 +1,6 @@
1
1
  import { describe, it } from "bun:test";
2
- import { encodeTest, encodeSnapshotTest, encodeFailTest } from "../utils.test";
3
- import * as st from "../index";
2
+ import { encodeTest, encodeSnapshotTest, encodeFailTest } from "../utils.test.js";
3
+ import * as st from "../index.js";
4
4
  describe("st.toBytes", () => {
5
5
  it("encode correctly", () => {
6
6
  encodeTest(st.pipe(st.bytes(3), st.toBytes()), //
@@ -10,4 +10,4 @@
10
10
  *
11
11
  * `ArrayBuffer([0, 255, 0])` => `00FF00`
12
12
  */
13
- export declare function toHex(): import("./pipe").Transform<string, ArrayBuffer>;
13
+ export declare function toHex(): import("./pipe.js").Transform<string, ArrayBuffer>;
@@ -1,4 +1,4 @@
1
- import { encode } from "./encode";
1
+ import { encode } from "./encode.js";
2
2
  /**
3
3
  * Converts an ArrayBuffer to uppercase hex
4
4
  *
@@ -12,5 +12,8 @@ import { encode } from "./encode";
12
12
  * `ArrayBuffer([0, 255, 0])` => `00FF00`
13
13
  */
14
14
  export function toHex() {
15
- return encode((v) => Uint8Array.fromHex(v).buffer, (v) => new Uint8Array(v).toHex().toUpperCase());
15
+ return encode({
16
+ encode: (v) => Uint8Array.fromHex(v).buffer,
17
+ decode: (v) => new Uint8Array(v).toHex().toUpperCase(),
18
+ });
16
19
  }
@@ -1,6 +1,6 @@
1
1
  import { describe, it } from "bun:test";
2
- import { encodeTest, encodeSnapshotTest, encodeFailTest } from "../utils.test";
3
- import * as st from "../index";
2
+ import { encodeTest, encodeSnapshotTest, encodeFailTest } from "../utils.test.js";
3
+ import * as st from "../index.js";
4
4
  describe("st.toHex", () => {
5
5
  it("encode correctly", () => {
6
6
  encodeTest(st.pipe(st.bytes(3), st.toHex()), //
@@ -12,5 +12,5 @@ type TypedArrayConstructor<T extends TypedArray> = new (array: ArrayBuffer) => T
12
12
  * )
13
13
  * ```
14
14
  */
15
- export declare function toTypedArray<T extends TypedArray>(arrayType: TypedArrayConstructor<T>): import("./pipe").Transform<T, ArrayBuffer>;
15
+ export declare function toTypedArray<T extends TypedArray>(arrayType: TypedArrayConstructor<T>): import("./pipe.js").Transform<T, ArrayBuffer>;
16
16
  export {};
@@ -1,4 +1,4 @@
1
- import { encode } from "./encode";
1
+ import { encode } from "./encode.js";
2
2
  /**
3
3
  * Converts an `ArrayBuffer` to a `TypedArray`
4
4
  *
@@ -10,5 +10,8 @@ import { encode } from "./encode";
10
10
  * ```
11
11
  */
12
12
  export function toTypedArray(arrayType) {
13
- return encode((v) => v.buffer, (v) => new arrayType(v));
13
+ return encode({
14
+ encode: (v) => v.buffer,
15
+ decode: (v) => new arrayType(v),
16
+ });
14
17
  }
@@ -1,6 +1,6 @@
1
1
  import { describe, it } from "bun:test";
2
- import { encodeTest, encodeSnapshotTest, expectError } from "../utils.test";
3
- import * as st from "../index";
2
+ import { encodeTest, encodeSnapshotTest, expectError } from "../utils.test.js";
3
+ import * as st from "../index.js";
4
4
  describe("st.toArrayBuffer", () => {
5
5
  it("encode correctly", () => {
6
6
  encodeTest(st.pipe(st.bytes(6), //
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;
@@ -1,2 +1,3 @@
1
- export { lazy } from "./lazy";
2
- export { createRememberedValue } from "./remember";
1
+ export { lazy } from "./lazy.js";
2
+ export { createReference } from "./reference.js";
3
+ export { createRememberedValue } from "./remember.js";
@@ -1,2 +1,3 @@
1
- export { lazy } from "./lazy";
2
- export { createRememberedValue } from "./remember";
1
+ export { lazy } from "./lazy.js";
2
+ export { createReference } from "./reference.js";
3
+ export { createRememberedValue } from "./remember.js";
@@ -1,2 +1,2 @@
1
- import type { Serializer } from "../types";
1
+ import type { Serializer } from "../types.js";
2
2
  export declare function lazy<TIn, TOut = TIn>(type: () => Serializer<TIn, TOut>): Serializer<TIn, TOut>;
@@ -1,11 +1,11 @@
1
1
  export function lazy(type) {
2
2
  let _size;
3
3
  let resolve = () => {
4
- const t = type();
5
- _size = t.size ?? undefined;
6
- serializer.read = t.read;
7
- serializer.write = t.write;
8
4
  resolve = () => { };
5
+ const { size, write, read } = type();
6
+ _size = size ?? undefined;
7
+ serializer.read = read;
8
+ serializer.write = write;
9
9
  };
10
10
  const serializer = {
11
11
  get size() {
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from "bun:test";
2
- import { encodeTest } from "../utils.test";
3
- import * as st from "../index";
2
+ import { encodeTest } from "../utils.test.js";
3
+ import * as st from "../index.js";
4
4
  describe("st.lazy", () => {
5
5
  it("encodes correctly", () => {
6
6
  const spec = st.lazy(() => st.object({
@@ -0,0 +1,18 @@
1
+ import type { Serializer } from "../types.js";
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.js";
3
+ import * as st from "../index.js";
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
+ });