Skip to content

v5.1.0

Choose a tag to compare

@DZakh DZakh released this 12 Oct 11:25
· 545 commits to main since this release

JS/TS as a full-fledged target of rescript-struct

Starting from the version JS/TS API users are as crucial as ReScript ones. rescript-struct has a unique design combining good DX, a small JS footprint, and insane performance. I see how it can benefit not only the ReScript ecosystem but also become a good fit for JS/TS performance-critical projects.

To support the vision, I've split the documentation into 4 parts:

Now, each target audience will be able to find as detailed answers as possible without looking at the source code. Also, I've added the Table of contents section to improve your reading experience.

Comparison section

When talking about statistics, I prefer numbers over gut feeling. So, I've prepared a comparison section where I compare rescript-struct with the currently hyping Zod and Valibot libraries. I've tried to highlight the strong sides of each library and keep it as honest as possible. Here's the gist of the comparison:

rescript-struct@5.1.0 Zod@3.22.2 Valibot@0.18.0
Total size (minified + gzipped) 9.67 kB 13.4 kB 6.73 kB
JS/TS API example size (minified + gzipped) 5.53 kB 12.8 kB 965 B
Performance (nested object parse) 153,787 ops/ms 1,177 ops/ms 3,562 ops/ms
Eval-free
Codegen-free (Doesn't need compiler)
Ecosystem ⭐️ ⭐️⭐️⭐️⭐️⭐️ ⭐️⭐️

Improved tree-shaking

When Valibot came out, it became a role model in terms of the JS footprint for parsing libraries. While having a similar modular design with small independent functions, I've decided to put some effort into reducing rescript-struct bundle size as much as possible. So, if we are talking of the JS/TS API example size, here are the improvements in the release:

V.5.0.1 V5.1.0 Diff
JS/TS API example size 17.3 kB 15.3 kB -2 kB
JS/TS API example size (minified + gzipped) 6.08 kB 5.53 kB -0.55 kB

JS/TS API enrichment

S.merge (Not available for ReScript users)

You can add additional fields to an object schema with the merge function.

const baseTeacherStruct = S.object({ students: S.array(S.string) });
const hasIDStruct = S.object({ id: S.string });

const teacherStruct = S.merge(baseTeacherStruct, hasIDStruct);
type Teacher = S.Output<typeof teacherStruct>; // => { students: string[], id: string }

🧠 The function will throw if the structs share keys. The returned schema also inherits the "unknownKeys" policy (strip/strict) of B.

Added missing primitive S.undefined

// empty type
S.undefined;

Alias for S.unit in ReScript API.

Advanced object struct

Sometimes you want to transform the data coming to your system. You can easily do it by passing a function to the S.object struct.

const userStruct = S.object((s) => ({
  id: s.field("USER_ID", S.number),
  name: s.field("USER_NAME", S.string),
}));

S.parseOrThrow(userStruct, {
  USER_ID: 1,
  USER_NAME: "John",
});
// => returns { id: 1, name: "John" }

// Infer output TypeScript type of the userStruct
type User = S.Output<typeof userStruct>; // { id: number; name: string }

Compared to using S.transform, the approach has 0 performance overhead. Also, you can use the same struct to transform the parsed data back to the initial format:

S.serializeOrThrow(userStruct, {
  id: 1,
  name: "John",
});
// => returns { USER_ID: 1, USER_NAME: "John" }

Advanced tuple struct

Sometimes you want to transform incoming tuples to a more convenient data-structure. To do this you can pass a function to the S.tuple struct.

const athleteStruct = S.tuple((s) => ({
  name: s.item(0, S.string),
  jerseyNumber: s.item(1, S.number),
  statistics: s.item(
    2,
    S.object({
      pointsScored: S.number,
    })
  ),
}));

type Athlete = S.Output<typeof athleteStruct>;
// type Athlete = {
//   name: string;
//   jerseyNumber: number;
//   statistics: {
//     pointsScored: number;
//   };
// }

The same as for advanced objects, you can use the same struct for transforming the parsed data back to the initial format. Also, it has 0 performance overhead and is as fast as parsing tuples without the transformation.

name

S.name(S.literal({ abc: 123 }));
// `Literal({"abc": 123})`

Used internally for readable error messages.

🧠 Subject to change

setName

const struct = S.setName(S.literal({ abc: 123 }, "Abc"));

S.name(struct);
// `Abc`

You can customise a struct name using S.setName.

Other changes

  • Fixed TS type for S.literal to support any JS value
  • Added S.Error.reason helper to get an error reason without location
  • Documented S.classify/S.name/S.setName for ReScript users