diff --git a/docs/js-usage.md b/docs/js-usage.md index d51d7466..d6e97821 100644 --- a/docs/js-usage.md +++ b/docs/js-usage.md @@ -414,6 +414,66 @@ S.arrayMinLength(S.array(S.string) 5); // Array must be 5 or more items long S.arrayLength(S.array(S.string) 5); // Array must be exactly 5 items long ``` +### Unnest + +```ts +const schema = S.unnest( + S.schema({ + id: S.string, + name: S.nullable(S.string), + deleted: S.boolean, + }) +); + +const value = S.reverseConvertOrThrow( + [ + { id: "0", name: "Hello", deleted: false }, + { id: "1", name: undefined, deleted: true }, + ], + schema +); +// [["0", "1"], ["Hello", null], [false, true]] +``` + +The helper function is inspired by the article [Boosting Postgres INSERT Performance by 2x With UNNEST](https://www.timescale.com/blog/boosting-postgres-insert-performance). It allows you to flatten a nested array of objects into arrays of values by field. + +The main concern of the approach described in the article is usability. And ReScript Schema completely solves the problem, providing a simple and intuitive API that is even more performant than `S.array`. + +
+ + +Checkout the compiled code yourself: + + +```javascript +(i) => { + let v1 = [new Array(i.length), new Array(i.length), new Array(i.length)]; + for (let v0 = 0; v0 < i.length; ++v0) { + let v3 = i[v0]; + try { + let v4 = v3["name"], + v5; + if (v4 !== void 0) { + v5 = v4; + } else { + v5 = null; + } + v1[0][v0] = v3["id"]; + v1[1][v0] = v5; + v1[2][v0] = v3["deleted"]; + } catch (v2) { + if (v2 && v2.s === s) { + v2.path = "" + "[\"'+v0+'\"]" + v2.path; + } + throw v2; + } + } + return v1; +}; +``` + +
+ ## Tuples Unlike arrays, tuples have a fixed number of elements and each element can have a different type. diff --git a/docs/rescript-usage.md b/docs/rescript-usage.md index a6a7b34d..3d754a26 100644 --- a/docs/rescript-usage.md +++ b/docs/rescript-usage.md @@ -39,6 +39,7 @@ - [Enums](#enums) - [`array`](#array) - [`list`](#list) + - [`unnest`](#unnest) - [`tuple`](#tuple) - [`tuple1` - `tuple3`](#tuple1---tuple3) - [`dict`](#dict) @@ -892,6 +893,60 @@ let schema = S.list(S.string) The `S.list` schema represents an array of data of a specific type which is transformed to ReScript's list data-structure. +### **`unnest`** + +`S.t<'value> => S.t>` + +```rescript +let schema = S.unnest(S.schema(s => { + id: s.matches(S.string), + name: s.matches(S.null(S.string)), + deleted: s.matches(S.bool), +})) + +[{id: "0", name: Some("Hello"), deleted: false}, {id: "1", name: None, deleted: true}]->S.reverseConvertOrThrow(schema) +// [["0", "1"], ["Hello", null], [false, true]] +``` + +The helper function is inspired by the article [Boosting Postgres INSERT Performance by 2x With UNNEST](https://www.timescale.com/blog/boosting-postgres-insert-performance). It allows you to flatten a nested array of objects into arrays of values by field. + +The main concern of the approach described in the article is usability. And ReScript Schema completely solves the problem, providing a simple and intuitive API that is even more performant than `S.array`. + +
+ + +Checkout the compiled code yourself: + + +```javascript +(i) => { + let v1 = [new Array(i.length), new Array(i.length), new Array(i.length)]; + for (let v0 = 0; v0 < i.length; ++v0) { + let v3 = i[v0]; + try { + let v4 = v3["name"], + v5; + if (v4 !== void 0) { + v5 = v4; + } else { + v5 = null; + } + v1[0][v0] = v3["id"]; + v1[1][v0] = v5; + v1[2][v0] = v3["deleted"]; + } catch (v2) { + if (v2 && v2.s === s) { + v2.path = "" + "[\"'+v0+'\"]" + v2.path; + } + throw v2; + } + } + return v1; +}; +``` + +
+ ### **`tuple`** `(S.Tuple.s => 'value) => S.t<'value>` diff --git a/package.json b/package.json index e0645665..c25a075c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rescript-schema", - "version": "9.0.1", + "version": "9.1.0", "description": "🧬 The fastest parser in the entire JavaScript ecosystem with a focus on small bundle size and top-notch DX", "keywords": [ "ReScript", diff --git a/packages/tests/src/core/S_test.ts b/packages/tests/src/core/S_test.ts index c506c654..f401746d 100644 --- a/packages/tests/src/core/S_test.ts +++ b/packages/tests/src/core/S_test.ts @@ -1708,6 +1708,44 @@ test("Tuple types", (t) => { t.pass(); }); +test("Unnest schema", (t) => { + const schema = S.unnest( + S.schema({ + id: S.string, + name: S.nullable(S.string), + deleted: S.boolean, + }) + ); + + const value = S.reverseConvertOrThrow( + [ + { id: "0", name: "Hello", deleted: false }, + { id: "1", name: undefined, deleted: true }, + ], + schema + ); + + let expected: typeof value = [ + ["0", "1"], + ["Hello", null], + [false, true], + ]; + + t.deepEqual(value, expected); + + expectType< + SchemaEqual< + typeof schema, + { + id: string; + name: string | undefined; + deleted: boolean; + }[], + (string[] | boolean[] | (string | null)[])[] + > + >(true); +}); + test("Tuple with transform to object", (t) => { let pointSchema = S.tuple((s) => { s.tag(0, "point"); diff --git a/packages/tests/src/core/S_unnest_test.res b/packages/tests/src/core/S_unnest_test.res index 0170d724..f888da36 100644 --- a/packages/tests/src/core/S_unnest_test.res +++ b/packages/tests/src/core/S_unnest_test.res @@ -42,6 +42,21 @@ test("Successfully parses and reverse converts a simple object with unnest", t = %raw(`[["a", "b"], [0, 1]]`), (), ) + + let example = S.unnest( + S.schema(s => + { + "id": s.matches(S.string), + "name": s.matches(S.null(S.string)), + "deleted": s.matches(S.bool), + } + ), + ) + t->U.assertCompiledCode( + ~schema=example, + ~op=#ReverseConvert, + `i=>{let v1=[new Array(i.length),new Array(i.length),new Array(i.length),];for(let v0=0;v0 { diff --git a/src/S.d.ts b/src/S.d.ts index 6f860296..30603941 100644 --- a/src/S.d.ts +++ b/src/S.d.ts @@ -204,6 +204,15 @@ export const array: ( schema: Schema ) => Schema; +export const unnest: >( + schema: Schema +) => Schema< + Output[], + { + [K in keyof Input]: Input[K][]; + }[keyof Input][] +>; + export const record: ( schema: Schema ) => Schema, Record>; diff --git a/src/S.js b/src/S.js index dc731278..14e41fee 100644 --- a/src/S.js +++ b/src/S.js @@ -14,6 +14,7 @@ export const optional = S.js_optional; export const nullable = S.$$null; export const nullish = S.nullable; export const array = S.array; +export const unnest = S.unnest; export const record = S.dict; export const jsonString = S.jsonString; export const union = S.js_union;