Repository: RobinTail/express-zod-api (ts)

Issue:
Async refinements
Hello,

First of all, thank you very much for your package!

I'm starting using it and I'm running into an issue. I have a piece of code that looks like this:
```ts
input: z.object({
        username: z.string().nonempty().max(100).refine(async username => {
            return await someAsyncFunction(username} == 0
       })
}),
```
I'm doing an async refinement, which should be allowed according to Zod docs. But I am getting this error:
`Async refinement encountered during synchronous parse operation. Use .parseAsync instead.`.

It looks like my `username` is not being parsed with parseAsync() but with parse(). Is the fact that we can't use async refinements on purpose?

Pull request description:
Issue 269: async refinements
Closes #269 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 950e9151..fd713cfa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
 
 ## Version 5
 
+### v5.3.1
+
+- Fixed issue #269: async refinements in I/O schemas of endpoints and middlewares.
+  - There was an error `Async refinement encountered during synchronous parse operation. Use .parseAsync instead.`
+
 ### v5.3.0
 
 - Supporting Node 17.
diff --git a/src/endpoint.ts b/src/endpoint.ts
index 1d298e2c..6d49c449 100644
--- a/src/endpoint.ts
+++ b/src/endpoint.ts
@@ -196,9 +196,9 @@ export class Endpoint<
     response.set("Access-Control-Allow-Headers", "content-type");
   }
 
-  #parseOutput(output: any) {
+  async #parseOutput(output: any) {
     try {
-      return this.outputSchema.parse(output);
+      return await this.outputSchema.parseAsync(output);
     } catch (e) {
       if (e instanceof z.ZodError) {
         throw new z.ZodError([
@@ -231,7 +231,7 @@ export class Endpoint<
     const options: any = {};
     let isStreamClosed = false;
     for (const def of this.middlewares) {
-      input = { ...input, ...def.input.parse(input) }; // middleware can transform the input types
+      Object.assign(input, await def.input.parseAsync(input)); // middleware can transform the input types
       Object.assign(
         options,
         await def.middleware({
@@ -264,7 +264,7 @@ export class Endpoint<
     logger: Logger;
   }) {
     return this.handler({
-      input: this.inputSchema.parse(input), // final input types transformations for handler,
+      input: await this.inputSchema.parseAsync(input), // final input types transformations for handler,
       options,
       logger,
     });
@@ -336,7 +336,7 @@ export class Endpoint<
       if (isStreamClosed) {
         return;
       }
-      output = this.#parseOutput(
+      output = await this.#parseOutput(
         await this.#parseAndRunHandler({ input, options, logger })
       );
     } catch (e) {


Test patch:
diff --git a/tests/unit/endpoint.spec.ts b/tests/unit/endpoint.spec.ts
index 22f515f5..0935e18b 100644
--- a/tests/unit/endpoint.spec.ts
+++ b/tests/unit/endpoint.spec.ts
@@ -458,4 +458,41 @@ describe("Endpoint", () => {
       });
     });
   });
+
+  describe("Issue #269: Async refinements", () => {
+    test("should handle async refinements in input, output and middleware", async () => {
+      const endpoint = new EndpointsFactory(defaultResultHandler)
+        .addMiddleware({
+          input: z.object({
+            m: z.number().refine(async (m) => m < 10),
+          }),
+          middleware: async () => ({}),
+        })
+        .build({
+          methods: ["post"],
+          input: z.object({
+            n: z.number().refine(async (n) => n > 100),
+          }),
+          output: z.object({
+            str: z.string().refine(async (str) => str.length > 3),
+          }),
+          handler: async () => ({
+            str: "This is fine",
+          }),
+        });
+      const { responseMock } = await testEndpoint({
+        endpoint,
+        requestProps: {
+          method: "POST",
+          body: { n: 123, m: 5 },
+        },
+      });
+      expect(responseMock.json).toHaveBeenCalledWith({
+        status: "success",
+        data: {
+          str: "This is fine",
+        },
+      });
+    });
+  });
 });