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 |
Nested object parsing | 153,787 ops/ms | 1,177 ops/ms | 3,562 ops/ms |
Compile + Nested object parsing | 54 ops/ms | 110 ops/ms | 1,937 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