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 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":"7","hashOfConfig":"8"},{"size":438,"mtime":1637877226360,"results":"9","hashOfConfig":"8"},{"size":1085,"mtime":1637877476573,"results":"10","hashOfConfig":"8"},{"size":1293,"mtime":1637876566355,"results":"11","hashOfConfig":"8"},{"size":1330,"mtime":1637902697212,"results":"12","hashOfConfig":"8"},{"size":2220,"mtime":1637902736281,"results":"13","hashOfConfig":"8"},{"filePath":"14","messages":"15","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"bt07le",{"filePath":"16","messages":"17","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"18","messages":"19","errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"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},"/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",["26"],"/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",["27"],"/Users/aaronpettengill/dev/remix-validated-form/test-app/cypress/integration/validation-with-fetchers.ts",[],{"ruleId":"28","severity":1,"message":"29","line":2,"column":23,"nodeType":"30","messageId":"31","endLine":2,"endColumn":39},{"ruleId":"28","severity":1,"message":"32","line":1,"column":26,"nodeType":"30","messageId":"31","endLine":1,"endColumn":39},"@typescript-eslint/no-unused-vars","'ValidationResult' is defined but never used.","Identifier","unusedVar","'useActionData' is defined but never used."]
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 = { firstName: "John", lastName: 123 };
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,3 @@
1
+ import type { z } from "zod";
2
+ import { Validator } from "..";
3
+ export declare function withZod<T>(zodSchema: z.Schema<T>): Validator<T>;
@@ -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 = { firstName: "John", lastName: 123 };
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,3 @@
1
+ import type { z } from "zod";
2
+ import { Validator } from "..";
3
+ export declare function withZod<T>(zodSchema: z.Schema<T>): Validator<T>;
@@ -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.0.0",
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"