remix-validated-form 1.0.0 → 1.1.0
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/.eslintcache +1 -1
- package/README.md +1 -1
- package/browser/validation/validation.test.js +65 -1
- package/browser/validation/withZod.d.ts +3 -0
- package/browser/validation/withZod.js +35 -0
- package/build/validation/validation.test.js +65 -1
- package/build/validation/withZod.d.ts +3 -0
- package/build/validation/withZod.js +39 -0
- package/package.json +3 -2
package/.eslintcache
CHANGED
@@ -1 +1 @@
|
|
1
|
-
[{"/Users/aaronpettengill/dev/remix-validated-form/src/server.ts":"1","/Users/aaronpettengill/dev/remix-validated-form/src/validation/types.ts":"2","/Users/aaronpettengill/dev/remix-validated-form/src/validation/withYup.ts":"3","/Users/aaronpettengill/dev/remix-validated-form/test-app/app/routes/validation.tsx":"4","/Users/aaronpettengill/dev/remix-validated-form/test-app/app/routes/validation-fetcher.tsx":"5","/Users/aaronpettengill/dev/remix-validated-form/test-app/cypress/integration/validation-with-fetchers.ts":"6"},{"size":207,"mtime":1637876506168,"results":"
|
1
|
+
[{"/Users/aaronpettengill/dev/remix-validated-form/src/server.ts":"1","/Users/aaronpettengill/dev/remix-validated-form/src/validation/types.ts":"2","/Users/aaronpettengill/dev/remix-validated-form/src/validation/withYup.ts":"3","/Users/aaronpettengill/dev/remix-validated-form/test-app/app/routes/validation.tsx":"4","/Users/aaronpettengill/dev/remix-validated-form/test-app/app/routes/validation-fetcher.tsx":"5","/Users/aaronpettengill/dev/remix-validated-form/test-app/cypress/integration/validation-with-fetchers.ts":"6","/Users/aaronpettengill/dev/remix-validated-form/src/validation/validation.test.ts":"7","/Users/aaronpettengill/dev/remix-validated-form/src/validation/withZod.ts":"8"},{"size":207,"mtime":1637876506168,"results":"9","hashOfConfig":"10"},{"size":438,"mtime":1637877226360,"results":"11","hashOfConfig":"10"},{"size":1085,"mtime":1637877476573,"results":"12","hashOfConfig":"10"},{"size":1293,"mtime":1637876566355,"results":"13","hashOfConfig":"10"},{"size":1330,"mtime":1637902697212,"results":"14","hashOfConfig":"10"},{"size":2220,"mtime":1637902736281,"results":"15","hashOfConfig":"10"},{"size":3526,"mtime":1638155421909,"results":"16","hashOfConfig":"10"},{"size":1168,"mtime":1638155747107,"results":"17","hashOfConfig":"10"},{"filePath":"18","messages":"19","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"bt07le",{"filePath":"20","messages":"21","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"22","messages":"23","errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"24","messages":"25","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"26","messages":"27","errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"28","messages":"29","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"30","messages":"31","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"32","messages":"33","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/aaronpettengill/dev/remix-validated-form/src/server.ts",[],"/Users/aaronpettengill/dev/remix-validated-form/src/validation/types.ts",[],"/Users/aaronpettengill/dev/remix-validated-form/src/validation/withYup.ts",["34"],"/Users/aaronpettengill/dev/remix-validated-form/test-app/app/routes/validation.tsx",[],"/Users/aaronpettengill/dev/remix-validated-form/test-app/app/routes/validation-fetcher.tsx",["35"],"/Users/aaronpettengill/dev/remix-validated-form/test-app/cypress/integration/validation-with-fetchers.ts",[],"/Users/aaronpettengill/dev/remix-validated-form/src/validation/validation.test.ts",[],"/Users/aaronpettengill/dev/remix-validated-form/src/validation/withZod.ts",[],{"ruleId":"36","severity":1,"message":"37","line":2,"column":23,"nodeType":"38","messageId":"39","endLine":2,"endColumn":39},{"ruleId":"36","severity":1,"message":"40","line":1,"column":26,"nodeType":"38","messageId":"39","endLine":1,"endColumn":39},"@typescript-eslint/no-unused-vars","'ValidationResult' is defined but never used.","Identifier","unusedVar","'useActionData' is defined but never used."]
|
package/README.md
CHANGED
@@ -129,7 +129,7 @@ export default function MyForm() {
|
|
129
129
|
|
130
130
|
# Validation Library Support
|
131
131
|
|
132
|
-
This library currently includes an out-of-the-box adapter for `yup`,
|
132
|
+
This library currently includes an out-of-the-box adapter for `yup` and `zod`,
|
133
133
|
but you can easily support whatever library you want by creating your own adapter.
|
134
134
|
|
135
135
|
And if you create an adapter for a library, feel free to make a PR on this library to add official support 😊
|
@@ -1,5 +1,7 @@
|
|
1
1
|
import * as yup from "yup";
|
2
|
+
import { z } from "zod";
|
2
3
|
import { withYup } from "..";
|
4
|
+
import { withZod } from "./withZod";
|
3
5
|
const validationTestCases = [
|
4
6
|
{
|
5
7
|
name: "yup",
|
@@ -9,6 +11,14 @@ const validationTestCases = [
|
|
9
11
|
age: yup.number(),
|
10
12
|
})),
|
11
13
|
},
|
14
|
+
{
|
15
|
+
name: "zod",
|
16
|
+
validator: withZod(z.object({
|
17
|
+
firstName: z.string().nonempty(),
|
18
|
+
lastName: z.string().nonempty(),
|
19
|
+
age: z.optional(z.number()),
|
20
|
+
})),
|
21
|
+
},
|
12
22
|
];
|
13
23
|
// Not going to enforce exact error strings here
|
14
24
|
const anyString = expect.any(String);
|
@@ -40,7 +50,10 @@ describe("Validation", () => {
|
|
40
50
|
});
|
41
51
|
describe("validateField", () => {
|
42
52
|
it("should not return an error if field is valid", () => {
|
43
|
-
const obj = {
|
53
|
+
const obj = {
|
54
|
+
firstName: "John",
|
55
|
+
lastName: {}, // invalid, but we should only be validating firstName
|
56
|
+
};
|
44
57
|
expect(validator.validateField(obj, "firstName")).toEqual({
|
45
58
|
error: undefined,
|
46
59
|
});
|
@@ -54,3 +67,54 @@ describe("Validation", () => {
|
|
54
67
|
});
|
55
68
|
});
|
56
69
|
});
|
70
|
+
describe("withZod", () => {
|
71
|
+
it("returns coherent errors for complex schemas", () => {
|
72
|
+
const schema = z.union([
|
73
|
+
z.object({
|
74
|
+
type: z.literal("foo"),
|
75
|
+
foo: z.string(),
|
76
|
+
}),
|
77
|
+
z.object({
|
78
|
+
type: z.literal("bar"),
|
79
|
+
bar: z.string(),
|
80
|
+
}),
|
81
|
+
]);
|
82
|
+
const obj = {
|
83
|
+
type: "foo",
|
84
|
+
bar: 123,
|
85
|
+
foo: 123,
|
86
|
+
};
|
87
|
+
expect(withZod(schema).validate(obj)).toEqual({
|
88
|
+
data: undefined,
|
89
|
+
error: {
|
90
|
+
type: anyString,
|
91
|
+
bar: anyString,
|
92
|
+
foo: anyString,
|
93
|
+
},
|
94
|
+
});
|
95
|
+
});
|
96
|
+
it("returns errors for fields that are unions", () => {
|
97
|
+
const schema = z.object({
|
98
|
+
field1: z.union([z.literal("foo"), z.literal("bar")]),
|
99
|
+
field2: z.union([z.literal("foo"), z.literal("bar")]),
|
100
|
+
});
|
101
|
+
const obj = {
|
102
|
+
field1: "a value",
|
103
|
+
// field2 missing
|
104
|
+
};
|
105
|
+
const validator = withZod(schema);
|
106
|
+
expect(validator.validate(obj)).toEqual({
|
107
|
+
data: undefined,
|
108
|
+
error: {
|
109
|
+
field1: anyString,
|
110
|
+
field2: anyString,
|
111
|
+
},
|
112
|
+
});
|
113
|
+
expect(validator.validateField(obj, "field1")).toEqual({
|
114
|
+
error: anyString,
|
115
|
+
});
|
116
|
+
expect(validator.validateField(obj, "field2")).toEqual({
|
117
|
+
error: anyString,
|
118
|
+
});
|
119
|
+
});
|
120
|
+
});
|
@@ -0,0 +1,35 @@
|
|
1
|
+
const getIssuesForError = (err) => {
|
2
|
+
return err.issues.flatMap((issue) => {
|
3
|
+
if ("unionErrors" in issue) {
|
4
|
+
return issue.unionErrors.flatMap((err) => getIssuesForError(err));
|
5
|
+
}
|
6
|
+
else {
|
7
|
+
return [issue];
|
8
|
+
}
|
9
|
+
});
|
10
|
+
};
|
11
|
+
export function withZod(zodSchema) {
|
12
|
+
return {
|
13
|
+
validate: (value) => {
|
14
|
+
const result = zodSchema.safeParse(value);
|
15
|
+
if (result.success)
|
16
|
+
return { data: result.data, error: undefined };
|
17
|
+
const fieldErrors = {};
|
18
|
+
getIssuesForError(result.error).forEach((issue) => {
|
19
|
+
const path = issue.path.join(".");
|
20
|
+
if (!fieldErrors[path])
|
21
|
+
fieldErrors[path] = issue.message;
|
22
|
+
});
|
23
|
+
return { error: fieldErrors, data: undefined };
|
24
|
+
},
|
25
|
+
validateField: (data, field) => {
|
26
|
+
var _a;
|
27
|
+
const result = zodSchema.safeParse(data);
|
28
|
+
if (result.success)
|
29
|
+
return { error: undefined };
|
30
|
+
return {
|
31
|
+
error: (_a = getIssuesForError(result.error).find((issue) => issue.path.join(".") === field)) === null || _a === void 0 ? void 0 : _a.message,
|
32
|
+
};
|
33
|
+
},
|
34
|
+
};
|
35
|
+
}
|
@@ -20,7 +20,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
20
|
};
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
22
22
|
const yup = __importStar(require("yup"));
|
23
|
+
const zod_1 = require("zod");
|
23
24
|
const __1 = require("..");
|
25
|
+
const withZod_1 = require("./withZod");
|
24
26
|
const validationTestCases = [
|
25
27
|
{
|
26
28
|
name: "yup",
|
@@ -30,6 +32,14 @@ const validationTestCases = [
|
|
30
32
|
age: yup.number(),
|
31
33
|
})),
|
32
34
|
},
|
35
|
+
{
|
36
|
+
name: "zod",
|
37
|
+
validator: (0, withZod_1.withZod)(zod_1.z.object({
|
38
|
+
firstName: zod_1.z.string().nonempty(),
|
39
|
+
lastName: zod_1.z.string().nonempty(),
|
40
|
+
age: zod_1.z.optional(zod_1.z.number()),
|
41
|
+
})),
|
42
|
+
},
|
33
43
|
];
|
34
44
|
// Not going to enforce exact error strings here
|
35
45
|
const anyString = expect.any(String);
|
@@ -61,7 +71,10 @@ describe("Validation", () => {
|
|
61
71
|
});
|
62
72
|
describe("validateField", () => {
|
63
73
|
it("should not return an error if field is valid", () => {
|
64
|
-
const obj = {
|
74
|
+
const obj = {
|
75
|
+
firstName: "John",
|
76
|
+
lastName: {}, // invalid, but we should only be validating firstName
|
77
|
+
};
|
65
78
|
expect(validator.validateField(obj, "firstName")).toEqual({
|
66
79
|
error: undefined,
|
67
80
|
});
|
@@ -75,3 +88,54 @@ describe("Validation", () => {
|
|
75
88
|
});
|
76
89
|
});
|
77
90
|
});
|
91
|
+
describe("withZod", () => {
|
92
|
+
it("returns coherent errors for complex schemas", () => {
|
93
|
+
const schema = zod_1.z.union([
|
94
|
+
zod_1.z.object({
|
95
|
+
type: zod_1.z.literal("foo"),
|
96
|
+
foo: zod_1.z.string(),
|
97
|
+
}),
|
98
|
+
zod_1.z.object({
|
99
|
+
type: zod_1.z.literal("bar"),
|
100
|
+
bar: zod_1.z.string(),
|
101
|
+
}),
|
102
|
+
]);
|
103
|
+
const obj = {
|
104
|
+
type: "foo",
|
105
|
+
bar: 123,
|
106
|
+
foo: 123,
|
107
|
+
};
|
108
|
+
expect((0, withZod_1.withZod)(schema).validate(obj)).toEqual({
|
109
|
+
data: undefined,
|
110
|
+
error: {
|
111
|
+
type: anyString,
|
112
|
+
bar: anyString,
|
113
|
+
foo: anyString,
|
114
|
+
},
|
115
|
+
});
|
116
|
+
});
|
117
|
+
it("returns errors for fields that are unions", () => {
|
118
|
+
const schema = zod_1.z.object({
|
119
|
+
field1: zod_1.z.union([zod_1.z.literal("foo"), zod_1.z.literal("bar")]),
|
120
|
+
field2: zod_1.z.union([zod_1.z.literal("foo"), zod_1.z.literal("bar")]),
|
121
|
+
});
|
122
|
+
const obj = {
|
123
|
+
field1: "a value",
|
124
|
+
// field2 missing
|
125
|
+
};
|
126
|
+
const validator = (0, withZod_1.withZod)(schema);
|
127
|
+
expect(validator.validate(obj)).toEqual({
|
128
|
+
data: undefined,
|
129
|
+
error: {
|
130
|
+
field1: anyString,
|
131
|
+
field2: anyString,
|
132
|
+
},
|
133
|
+
});
|
134
|
+
expect(validator.validateField(obj, "field1")).toEqual({
|
135
|
+
error: anyString,
|
136
|
+
});
|
137
|
+
expect(validator.validateField(obj, "field2")).toEqual({
|
138
|
+
error: anyString,
|
139
|
+
});
|
140
|
+
});
|
141
|
+
});
|
@@ -0,0 +1,39 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.withZod = void 0;
|
4
|
+
const getIssuesForError = (err) => {
|
5
|
+
return err.issues.flatMap((issue) => {
|
6
|
+
if ("unionErrors" in issue) {
|
7
|
+
return issue.unionErrors.flatMap((err) => getIssuesForError(err));
|
8
|
+
}
|
9
|
+
else {
|
10
|
+
return [issue];
|
11
|
+
}
|
12
|
+
});
|
13
|
+
};
|
14
|
+
function withZod(zodSchema) {
|
15
|
+
return {
|
16
|
+
validate: (value) => {
|
17
|
+
const result = zodSchema.safeParse(value);
|
18
|
+
if (result.success)
|
19
|
+
return { data: result.data, error: undefined };
|
20
|
+
const fieldErrors = {};
|
21
|
+
getIssuesForError(result.error).forEach((issue) => {
|
22
|
+
const path = issue.path.join(".");
|
23
|
+
if (!fieldErrors[path])
|
24
|
+
fieldErrors[path] = issue.message;
|
25
|
+
});
|
26
|
+
return { error: fieldErrors, data: undefined };
|
27
|
+
},
|
28
|
+
validateField: (data, field) => {
|
29
|
+
var _a;
|
30
|
+
const result = zodSchema.safeParse(data);
|
31
|
+
if (result.success)
|
32
|
+
return { error: undefined };
|
33
|
+
return {
|
34
|
+
error: (_a = getIssuesForError(result.error).find((issue) => issue.path.join(".") === field)) === null || _a === void 0 ? void 0 : _a.message,
|
35
|
+
};
|
36
|
+
},
|
37
|
+
};
|
38
|
+
}
|
39
|
+
exports.withZod = withZod;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "remix-validated-form",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.1.0",
|
4
4
|
"description": "Form component and utils for easy form validation in remix",
|
5
5
|
"browser": "./browser/index.js",
|
6
6
|
"main": "./build/index.js",
|
@@ -64,7 +64,8 @@
|
|
64
64
|
"react": "^17.0.2",
|
65
65
|
"ts-jest": "^27.0.7",
|
66
66
|
"typescript": "^4.5.2",
|
67
|
-
"yup": "^0.32.11"
|
67
|
+
"yup": "^0.32.11",
|
68
|
+
"zod": "^3.11.6"
|
68
69
|
},
|
69
70
|
"dependencies": {
|
70
71
|
"tiny-invariant": "^1.2.0"
|