@nerd-bible/valio 0.0.1

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 ADDED
@@ -0,0 +1,125 @@
1
+ # valio
2
+
3
+ Encode and decode to Typescript types with extensible error handling.
4
+
5
+ ## Why?
6
+
7
+ I like [Zod](https://zod.dev), but its codecs don't support
8
+ [custom error contexts.](https://github.com/colinhacks/zod/issues/)
9
+
10
+ ## Theory
11
+
12
+ - Data flows through "pipes" with specific input and output types.
13
+ - Both input and output are validated
14
+ - Pipes are bidirectional.
15
+ - Input -> output is "decoding"
16
+ - Output -> input is "encoding"
17
+ - When encoding or decoding a `Context` is passed which holds:
18
+ - The path to the current value
19
+ - Error formatting function
20
+ - Errors grouped by path
21
+
22
+ ## Practice
23
+
24
+ Use classes, inheritance, and result types. No errors are thrown.
25
+
26
+ ### Usage
27
+
28
+ For primitive types the input and output are the same.
29
+
30
+ ```ts
31
+ import * as v from "@nerd-bible/valio";
32
+
33
+ const schema = v.number(); // Pipe<number, number>
34
+
35
+ expect(schema.decode(5)).toEqual({ success: true, output: 5 });
36
+ expect(schema.decodeAny("5")).toEqual({
37
+ success: false,
38
+ errors: { ".": [{ input: "5", message: "not type number" }] },
39
+ });
40
+ ```
41
+
42
+ You can add custom checks, but be sure to provide context for error messages.
43
+
44
+ ```ts
45
+ import * as v from "@nerd-bible/valio";
46
+
47
+ const schema = v.number().refine((n) => n == 5, "eq", { n: 5 });
48
+
49
+ expect(schema.decode(5)).toEqual({ success: true, output: 5 });
50
+ expect(schema.encode(3)).toEqual({
51
+ success: false,
52
+ errors: { ".": [{ input: 3, message: "must be 5" }] },
53
+ });
54
+ ```
55
+
56
+ There are common builtin codecs for coercion.
57
+ ```ts
58
+ import * as v from "@nerd-bible/valio";
59
+
60
+ const schema = v.codecs.number();
61
+
62
+ expect(schema.decode("13")).toEqual({ success: true, output: 13 });
63
+ ```
64
+
65
+ ### Extending
66
+
67
+ To make a codec with a custom transformer, you can use `v.codecs.custom`.
68
+
69
+ ```ts
70
+ import * as v from "@nerd-bible/valio";
71
+
72
+ function transformToNumber(any: number): number {
73
+ if (typeof any == "number") return any;
74
+ return NaN;
75
+ }
76
+
77
+ const schema = v.codecs.custom(
78
+ v.union([v.string(), v.number(), v.null(), v.undefined()]),
79
+ v.number(),
80
+ { decode: transformToNumber },
81
+ );
82
+ ```
83
+
84
+ To make a pipe with any types and transformers, simply extend the class with
85
+ your input and output `HalfPipe`s.
86
+
87
+ ```ts
88
+ import { Pipe, HalfPipe } from "@nerd-bible/valio";
89
+
90
+ function transformToNumber(any: number): number {
91
+ if (typeof any == "number") return any;
92
+ return NaN;
93
+ }
94
+
95
+ class AnyToNumber extends Pipe<any, number> {
96
+ constructor() {
97
+ super(
98
+ new HalfPipe("any", (v): v is any => true, transformToNumber),
99
+ new HalfPipe("number", (v): v is number => typeof v == "number"),
100
+ );
101
+ }
102
+ }
103
+ ```
104
+
105
+ ### Custom error messages
106
+
107
+ Override `errorFmt` in `Context` and pass it to `encode` and `decode`.
108
+
109
+ ```ts
110
+ import * as v from "@nerd-bible/valio";
111
+
112
+ const schema = v.number().refine((n) => n == 5, "eq", { n: 5 });
113
+ class MyContext extends v.Context {
114
+ errorFmt() {
115
+ return "You done messed up";
116
+ }
117
+ }
118
+
119
+ expect(schema.decode(3, new MyContext())).toEqual({
120
+ success: false,
121
+ errors: { ".": [{ input: 3, message: "You done messed up" }] },
122
+ });
123
+ ```
124
+
125
+ Those are the highlights. Read the `.test.ts` files for the rest.
package/bun.lock ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "workspaces": {
4
+ "": {
5
+ "name": "valio",
6
+ "dependencies": {
7
+ "@biomejs/biome": "^2.3.0",
8
+ },
9
+ "devDependencies": {
10
+ "@types/bun": "latest",
11
+ },
12
+ "peerDependencies": {
13
+ "typescript": "^5",
14
+ },
15
+ },
16
+ },
17
+ "packages": {
18
+ "@biomejs/biome": ["@biomejs/biome@2.3.0", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.0", "@biomejs/cli-darwin-x64": "2.3.0", "@biomejs/cli-linux-arm64": "2.3.0", "@biomejs/cli-linux-arm64-musl": "2.3.0", "@biomejs/cli-linux-x64": "2.3.0", "@biomejs/cli-linux-x64-musl": "2.3.0", "@biomejs/cli-win32-arm64": "2.3.0", "@biomejs/cli-win32-x64": "2.3.0" }, "bin": { "biome": "bin/biome" } }, "sha512-shdUY5H3S3tJVUWoVWo5ua+GdPW5lRHf+b0IwZ4OC1o2zOKQECZ6l2KbU6t89FNhtd3Qx5eg5N7/UsQWGQbAFw=="],
19
+
20
+ "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-3cJVT0Z5pbTkoBmbjmDZTDFYxIkRcrs9sYVJbIBHU8E6qQxgXAaBfSVjjCreG56rfDuQBr43GzwzmaHPcu4vlw=="],
21
+
22
+ "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-6LIkhglh3UGjuDqJXsK42qCA0XkD1Ke4K/raFOii7QQPbM8Pia7Qj2Hji4XuF2/R78hRmEx7uKJH3t/Y9UahtQ=="],
23
+
24
+ "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-uhAsbXySX7xsXahegDg5h3CDgfMcRsJvWLFPG0pjkylgBb9lErbK2C0UINW52zhwg0cPISB09lxHPxCau4e2xA=="],
25
+
26
+ "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-nDksoFdwZ2YrE7NiYDhtMhL2UgFn8Kb7Y0bYvnTAakHnqEdb4lKindtBc1f+xg2Snz0JQhJUYO7r9CDBosRU5w=="],
27
+
28
+ "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.0", "", { "os": "linux", "cpu": "x64" }, "sha512-uxa8reA2s1VgoH8MhbGlCmMOt3JuSE1vJBifkh1ulaPiuk0SPx8cCdpnm9NWnTe2x/LfWInWx4sZ7muaXTPGGw=="],
29
+
30
+ "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.0", "", { "os": "linux", "cpu": "x64" }, "sha512-+i9UcJwl99uAhtRQDz9jUAh+Xkb097eekxs/D9j4deWDg5/yB/jPWzISe1nBHvlzTXsdUSj0VvB4Go2DSpKIMw=="],
31
+
32
+ "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-ynjmsJLIKrAjC3CCnKMMhzcnNy8dbQWjKfSU5YA0mIruTxBNMbkAJp+Pr2iV7/hFou+66ZSD/WV8hmLEmhUaXA=="],
33
+
34
+ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.0", "", { "os": "win32", "cpu": "x64" }, "sha512-zOCYmCRVkWXc9v8P7OLbLlGGMxQTKMvi+5IC4v7O8DkjLCOHRzRVK/Lno2pGZNo0lzKM60pcQOhH8HVkXMQdFg=="],
35
+
36
+ "@types/bun": ["@types/bun@1.2.23", "", { "dependencies": { "bun-types": "1.2.23" } }, "sha512-le8ueOY5b6VKYf19xT3McVbXqLqmxzPXHsQT/q9JHgikJ2X22wyTW3g3ohz2ZMnp7dod6aduIiq8A14Xyimm0A=="],
37
+
38
+ "@types/node": ["@types/node@24.6.2", "", { "dependencies": { "undici-types": "~7.13.0" } }, "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang=="],
39
+
40
+ "@types/react": ["@types/react@19.2.0", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA=="],
41
+
42
+ "bun-types": ["bun-types@1.2.23", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-R9f0hKAZXgFU3mlrA0YpE/fiDvwV0FT9rORApt2aQVWSuJDzZOyB5QLc0N/4HF57CS8IXJ6+L5E4W1bW6NS2Aw=="],
43
+
44
+ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
45
+
46
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
47
+
48
+ "undici-types": ["undici-types@7.13.0", "", {}, "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ=="],
49
+ }
50
+ }
@@ -0,0 +1,12 @@
1
+ import type { Context, Pipe, Result } from "./pipe";
2
+ import * as p from "./primitives";
3
+ export declare function custom<I, O>(input: Pipe<I, any>, output: Pipe<any, O>, codec: {
4
+ encode?(input: O, ctx: Context): Result<I>;
5
+ decode?(output: I, ctx: Context): Result<O>;
6
+ }): Pipe<I, O>;
7
+ export declare function number(parser?: typeof parseFloat): p.Comparable<string | number | null | undefined, number>;
8
+ export declare function boolean(opts: {
9
+ true?: string[];
10
+ false?: string[];
11
+ }): Pipe<any, boolean>;
12
+ //# sourceMappingURL=codecs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codecs.d.ts","sourceRoot":"","sources":["../src/codecs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACpD,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAGlC,wBAAgB,MAAM,CAAC,CAAC,EAAE,CAAC,EAC1B,KAAK,EAAE,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,EACnB,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EACpB,KAAK,EAAE;IACN,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;CAC5C,GACC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAMZ;AAED,wBAAgB,MAAM,CACrB,MAAM,oBAAa,GACjB,CAAC,CAAC,UAAU,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,MAAM,CAAC,CAkB1D;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE;IAC7B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAWrB"}
package/dist/codecs.js ADDED
@@ -0,0 +1,37 @@
1
+ import * as p from "./primitives";
2
+ import * as c from "./containers";
3
+ export function custom(input, output, codec) {
4
+ const res = output.clone();
5
+ res.i = input.i.clone();
6
+ res.i.transform = codec.decode;
7
+ res.o.transform = codec.encode;
8
+ return res;
9
+ }
10
+ export function number(parser = parseFloat) {
11
+ return custom(c.union([p.string(), p.number(), p.null(), p.undefined()]), p.number(), {
12
+ decode(input, ctx) {
13
+ if (typeof input == "number")
14
+ return { success: true, output: input };
15
+ if (input == null || input.toLowerCase() == "nan")
16
+ return { success: true, output: NaN };
17
+ const output = parser(input);
18
+ if (!isNaN(output))
19
+ return { success: true, output };
20
+ ctx.pushErrorFmt("coerce", input, { expected: "number" });
21
+ return { success: false, errors: ctx.errors };
22
+ },
23
+ });
24
+ }
25
+ export function boolean(opts) {
26
+ return custom(p.any(), p.boolean(), {
27
+ decode(input) {
28
+ if (typeof input === "string") {
29
+ if (opts.true?.includes(input))
30
+ return { success: true, output: true };
31
+ if (opts.false?.includes(input))
32
+ return { success: true, output: false };
33
+ }
34
+ return { success: true, output: Boolean(input) };
35
+ },
36
+ });
37
+ }
@@ -0,0 +1,54 @@
1
+ import { Context, Pipe } from "./pipe";
2
+ import type { Input, Output, Result } from "./pipe";
3
+ import * as p from "./primitives";
4
+ declare class ValioArray<T> extends p.Arrayish<any[], T[]> {
5
+ element: Pipe<any, T>;
6
+ constructor(element: Pipe<any, T>);
7
+ }
8
+ export declare function array<T>(element: Pipe<any, T>): ValioArray<T>;
9
+ declare class ValioRecord<K extends PropertyKey, V> extends Pipe<Record<any, any>, Record<K, V>> {
10
+ keyPipe: Pipe<any, K>;
11
+ valPipe: Pipe<any, V>;
12
+ constructor(keyPipe: Pipe<any, K>, valPipe: Pipe<any, V>);
13
+ }
14
+ export declare function record<K extends PropertyKey, V>(keyPipe: Pipe<any, K>, valPipe: Pipe<any, V>): ValioRecord<K, V>;
15
+ declare class Union<T extends Readonly<Pipe[]>> extends Pipe<Output<T[number]>, Output<T[number]>> {
16
+ options: T;
17
+ constructor(options: T);
18
+ }
19
+ export declare function union<T extends Readonly<Pipe[]>>(options: T): Union<T>;
20
+ type ObjectOutput<Shape extends Record<string, Pipe<any, any>>> = {
21
+ [K in keyof Shape]: Output<Shape[K]>;
22
+ };
23
+ type Mask<Keys extends PropertyKey> = {
24
+ [K in Keys]?: true;
25
+ };
26
+ type Identity<T> = T;
27
+ type Flatten<T> = Identity<{
28
+ [k in keyof T]: T[k];
29
+ }>;
30
+ type Extend<A extends Record<any, any>, B extends Record<any, any>> = Flatten<keyof A & keyof B extends never ? A & B : {
31
+ [K in keyof A as K extends keyof B ? never : K]: A[K];
32
+ } & {
33
+ [K in keyof B]: B[K];
34
+ }>;
35
+ declare class ValioObject<Shape extends Record<any, Pipe<any, any>>> extends Pipe<Record<any, any>, ObjectOutput<Shape>> {
36
+ shape: Shape;
37
+ isLoose: boolean;
38
+ constructor(shape: Shape, isLoose: boolean);
39
+ clone(): this;
40
+ protected transformInput(data: object, ctx: Context): Result<ObjectOutput<Shape>>;
41
+ protected typeCheckOutput(v: any): v is ObjectOutput<Shape>;
42
+ pick<M extends Mask<keyof Shape>>(mask: M): ValioObject<Flatten<Pick<Shape, Extract<keyof Shape, keyof M>>>>;
43
+ omit<M extends Mask<keyof Shape>>(mask: M): ValioObject<Flatten<Omit<Shape, Extract<keyof Shape, keyof M>>>>;
44
+ partial<M extends Mask<keyof Shape>>(mask: M): ValioObject<{
45
+ [k in keyof Shape]: k extends keyof M ? Pipe<Input<Shape[k]>, Output<Shape[k]> | undefined> : Shape[k];
46
+ }>;
47
+ extend<T extends Record<any, Pipe<any, any>>>(shape: T): ValioObject<Extend<Shape, T>>;
48
+ loose<T = any>(isLoose?: boolean): ValioObject<Shape & {
49
+ [k: string]: Pipe<T, T>;
50
+ }>;
51
+ }
52
+ export declare function object<Shape extends Record<any, Pipe<any, any>>>(shape: Shape, loose?: boolean): ValioObject<Shape>;
53
+ export {};
54
+ //# sourceMappingURL=containers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"containers.d.ts","sourceRoot":"","sources":["../src/containers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,192 @@
1
+ import { HalfPipe, Context, Pipe } from "./pipe";
2
+ import * as p from "./primitives";
3
+ class ValioArray extends p.Arrayish {
4
+ element;
5
+ constructor(element) {
6
+ super(new HalfPipe("array", (v) => Array.isArray(v), (input, ctx) => {
7
+ const output = new Array(input.length);
8
+ let success = true;
9
+ const length = ctx.jsonPath.length;
10
+ for (let i = 0; i < input.length; i++) {
11
+ ctx.jsonPath[length] = i.toString();
12
+ const decoded = element.decode(input[i], ctx);
13
+ if (decoded.success)
14
+ output[i] = decoded.output;
15
+ else
16
+ success = false;
17
+ }
18
+ ctx.jsonPath.length = length;
19
+ if (!success)
20
+ return { success, errors: ctx.errors };
21
+ return { success, output };
22
+ }), new HalfPipe(`array<${element.o.name}>`, (v) => {
23
+ if (!Array.isArray(v))
24
+ return false;
25
+ for (const e of v)
26
+ if (!element.o.typeCheck(e))
27
+ return false;
28
+ return true;
29
+ }));
30
+ this.element = element;
31
+ }
32
+ }
33
+ export function array(element) {
34
+ return new ValioArray(element);
35
+ }
36
+ class ValioRecord extends Pipe {
37
+ keyPipe;
38
+ valPipe;
39
+ constructor(keyPipe, valPipe) {
40
+ super(new HalfPipe("object", (v) => Object.prototype.toString.call(v) == "[object Object]", (input, ctx) => {
41
+ const output = {};
42
+ let success = true;
43
+ const length = ctx.jsonPath.length;
44
+ for (const key in input) {
45
+ ctx.jsonPath[length] = key;
46
+ const decodedKey = keyPipe.decode(key, ctx);
47
+ if (decodedKey.success) {
48
+ const decodedVal = valPipe.decode(input[key], ctx);
49
+ if (decodedVal.success) {
50
+ output[decodedKey.output] = decodedVal.output;
51
+ }
52
+ else {
53
+ success = false;
54
+ }
55
+ }
56
+ else {
57
+ success = false;
58
+ }
59
+ }
60
+ ctx.jsonPath.length = length;
61
+ if (!success)
62
+ return { success, errors: ctx.errors };
63
+ return { success, output };
64
+ }), new HalfPipe(`record<${keyPipe.o.name},${valPipe.o.name}>`, (v) => {
65
+ if (Object.prototype.toString.call(v) != "[object Object]")
66
+ return false;
67
+ for (const k in v) {
68
+ // Keys will always be strings.
69
+ // if (!keyPipe.o.typeCheck(k)) return false;
70
+ if (!valPipe.o.typeCheck(v[k]))
71
+ return false;
72
+ }
73
+ return true;
74
+ }));
75
+ this.keyPipe = keyPipe;
76
+ this.valPipe = valPipe;
77
+ }
78
+ }
79
+ export function record(keyPipe, valPipe) {
80
+ return new ValioRecord(keyPipe, valPipe);
81
+ }
82
+ class Union extends Pipe {
83
+ options;
84
+ constructor(options) {
85
+ const name = options.map((o) => o.o.name).join("|");
86
+ super(new HalfPipe(name, (v) => {
87
+ for (const f of options)
88
+ if (f.i.typeCheck(v))
89
+ return true;
90
+ return false;
91
+ }, (data, ctx) => {
92
+ const newCtx = ctx.clone();
93
+ for (const s in options) {
94
+ const decoded = options[s].decode(data, newCtx);
95
+ if (decoded.success)
96
+ return decoded;
97
+ }
98
+ Object.assign(ctx.errors, newCtx.errors);
99
+ return { success: false, errors: ctx.errors };
100
+ }), new HalfPipe(name, (v) => {
101
+ for (const f of options)
102
+ if (f.o.typeCheck(v))
103
+ return true;
104
+ return false;
105
+ }));
106
+ this.options = options;
107
+ }
108
+ }
109
+ export function union(options) {
110
+ return new Union(options);
111
+ }
112
+ class ValioObject extends Pipe {
113
+ shape;
114
+ isLoose;
115
+ constructor(shape, isLoose) {
116
+ super(new HalfPipe("object", (v) => Object.prototype.toString.call(v) == "[object Object]", (data, ctx) => this.transformInput(data, ctx)), new HalfPipe(`{${Object.entries(shape)
117
+ .map(([k, v]) => `${k}: ${v.o.name}`)
118
+ .join(",")}}`, (v) => this.typeCheckOutput(v)));
119
+ this.shape = shape;
120
+ this.isLoose = isLoose;
121
+ }
122
+ clone() {
123
+ return new ValioObject(this.shape, this.isLoose);
124
+ }
125
+ transformInput(data, ctx) {
126
+ const output = this.isLoose ? data : {};
127
+ let success = true;
128
+ const length = ctx.jsonPath.length;
129
+ // Always expect the shape since that's what typescript does.
130
+ for (const p in this.shape) {
131
+ ctx.jsonPath[length] = p;
132
+ const decoded = this.shape[p].decode(data[p], ctx);
133
+ if (decoded.success)
134
+ output[p] = decoded.output;
135
+ else {
136
+ success = false;
137
+ delete output[p];
138
+ }
139
+ }
140
+ ctx.jsonPath.length = length;
141
+ if (!success)
142
+ return { success, errors: ctx.errors };
143
+ return { success, output: output };
144
+ }
145
+ typeCheckOutput(v) {
146
+ if (Object.prototype.toString.call(v) != "[object Object]")
147
+ return false;
148
+ for (const s in this.shape)
149
+ if (!this.shape[s].o.typeCheck(v[s]))
150
+ return false;
151
+ return true;
152
+ }
153
+ pick(mask) {
154
+ const next = this.clone();
155
+ for (const k in next.shape) {
156
+ if (!mask[k])
157
+ delete next.shape[k];
158
+ }
159
+ return next;
160
+ }
161
+ omit(mask) {
162
+ const next = this.clone();
163
+ for (const k in next.shape) {
164
+ if (mask[k])
165
+ delete next.shape[k];
166
+ }
167
+ return next;
168
+ }
169
+ partial(mask) {
170
+ const next = this.clone();
171
+ for (const k in next.shape) {
172
+ if (mask[k]) {
173
+ // @ts-ignore
174
+ next.shape[k] = union([next.shape[k], p.undefined()]);
175
+ }
176
+ }
177
+ return next;
178
+ }
179
+ extend(shape) {
180
+ const next = this.clone();
181
+ Object.assign(next.shape, shape);
182
+ return next;
183
+ }
184
+ loose(isLoose = true) {
185
+ const next = this.clone();
186
+ next.isLoose = isLoose;
187
+ return next;
188
+ }
189
+ }
190
+ export function object(shape, loose = false) {
191
+ return new ValioObject(shape, loose);
192
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./pipe";
2
+ export * from "./primitives";
3
+ export * from "./containers";
4
+ export * as codecs from "./codecs";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./pipe";
2
+ export * from "./primitives";
3
+ export * from "./containers";
4
+ export * as codecs from "./codecs";
@@ -0,0 +1,2 @@
1
+ export default function format(name: string, props: Record<any, any>): string;
2
+ //# sourceMappingURL=en.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/locales/en.ts"],"names":[],"mappings":"AAmBA,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,MAAM,CAG5E"}
@@ -0,0 +1,21 @@
1
+ const templates = {
2
+ gt: "must be > {$n}",
3
+ lt: "must be < {$n}",
4
+ gte: "must be >= {$n}",
5
+ lte: "must be <= {$n}",
6
+ eq: "must be {$n}",
7
+ minLength: "must have length <= {$n}",
8
+ maxLength: "must have length >= {$n}",
9
+ regex: "must match {$regex}",
10
+ type: "not type {$expected}",
11
+ coerce: "could not coerce to {$expected}",
12
+ };
13
+ function fmt(template, props) {
14
+ // You could use something like
15
+ // [MessageFormat](https://messageformat.unicode.org/) here.
16
+ return template.replace(/{\$(.*?)}/g, (_, g) => props[g]);
17
+ }
18
+ export default function format(name, props) {
19
+ const template = templates[name];
20
+ return template ? fmt(template, props) : `TODO: add template for ${name}`;
21
+ }
package/dist/pipe.d.ts ADDED
@@ -0,0 +1,66 @@
1
+ export type Error = {
2
+ input: any;
3
+ message: string;
4
+ };
5
+ export type Errors = {
6
+ [inputPath: string]: Error[];
7
+ };
8
+ export type Result<T> = {
9
+ success: true;
10
+ output: T;
11
+ } | {
12
+ success: false;
13
+ errors: any;
14
+ };
15
+ interface Check<T> {
16
+ valid(data: T, ctx: Context): boolean;
17
+ name: string;
18
+ props: Record<any, any>;
19
+ }
20
+ export declare class HalfPipe<I, O = never> {
21
+ /** The type name */
22
+ name: string;
23
+ /** The first check to run */
24
+ typeCheck: (v: any) => v is I;
25
+ /** Optional transform for pipe to run at end. Useful for containers */
26
+ transform?: ((v: I, ctx: Context) => Result<O>) | undefined;
27
+ constructor(
28
+ /** The type name */
29
+ name: string,
30
+ /** The first check to run */
31
+ typeCheck: (v: any) => v is I,
32
+ /** Optional transform for pipe to run at end. Useful for containers */
33
+ transform?: ((v: I, ctx: Context) => Result<O>) | undefined);
34
+ /** The second checks to run */
35
+ checks: Check<I>[];
36
+ clone(): this;
37
+ }
38
+ /** During encoding, decoding, or validation. */
39
+ export declare class Context {
40
+ jsonPath: (string | number)[];
41
+ errors: Errors;
42
+ errorFmt(name: string, props: Record<any, any>): any;
43
+ clone(): Context;
44
+ pushError(error: Error): void;
45
+ pushErrorFmt(name: string, input: any, props: Record<any, any>): void;
46
+ run<I, O>(input: any, halfPipe: HalfPipe<I, O>): Result<I>;
47
+ }
48
+ export declare class Pipe<I = any, O = any> {
49
+ i: HalfPipe<I, O>;
50
+ o: HalfPipe<O, I>;
51
+ constructor(i: HalfPipe<I, O>, o: HalfPipe<O, I>);
52
+ pipes: Pipe<any, any>[];
53
+ registry: Record<PropertyKey, any>;
54
+ clone(): this;
55
+ refine(valid: (data: O, ctx: Context) => boolean, name: string, props: Record<any, any>): this;
56
+ pipe<I2 extends O, O2>(pipe: Pipe<I2, O2>): Pipe<I, O2>;
57
+ decodeAny(input: any, ctx?: Context): Result<O>;
58
+ decode(input: I, ctx?: Context): Result<O>;
59
+ encodeAny(output: any, ctx?: Context): Result<I>;
60
+ encode(output: O, ctx?: Context): Result<I>;
61
+ register(key: PropertyKey, value: any): this;
62
+ }
63
+ export type Input<T extends Pipe> = Parameters<T["decode"]>[0];
64
+ export type Output<T extends Pipe> = Parameters<T["encode"]>[0];
65
+ export {};
66
+ //# sourceMappingURL=pipe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipe.d.ts","sourceRoot":"","sources":["../src/pipe.ts"],"names":[],"mappings":"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"}