diff --git a/apps/example-todo-app/next-env.d.ts b/apps/example-todo-app/next-env.d.ts
index 4f11a03dc..fd36f9494 100755
--- a/apps/example-todo-app/next-env.d.ts
+++ b/apps/example-todo-app/next-env.d.ts
@@ -1,5 +1,6 @@
///
///
+///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/apps/example-todo-app/next.config.js b/apps/example-todo-app/next.config.js
index 6f0005eab..dfbbfa8d2 100755
--- a/apps/example-todo-app/next.config.js
+++ b/apps/example-todo-app/next.config.js
@@ -1,15 +1,20 @@
// This file changes the routing to allow top-level prefixes
-module.exports = {
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ reactStrictMode: true,
async rewrites() {
return {
beforeFiles: [
// Nextjs by default requires a /api prefix, let's remove that
- {
- source: "/:path*",
- destination: "/api/:path*",
- },
+ // REVIEW: I was not able to make the redirect work
+ // {
+ // source: "/:path*",
+ // destination: "/api/:path*",
+ // },
],
}
},
}
+
+module.exports = nextConfig
diff --git a/apps/example-todo-app/package.json b/apps/example-todo-app/package.json
index 14fb1af17..508d6c7cd 100644
--- a/apps/example-todo-app/package.json
+++ b/apps/example-todo-app/package.json
@@ -21,13 +21,13 @@
"glob-promise": "4.2.2",
"micro": "9.3.4",
"mkdirp": "1.0.4",
- "next": "12.2.0",
+ "next": "^13.4.9",
"nextjs-middleware-wrappers": "^1.3.0",
"nextlove": "*",
"path-to-regexp": "6.2.1",
"raw-body": "2.3.2",
- "react": "18.2.0",
- "react-dom": "18.2.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
"uuid": "^8.3.2",
"zod": "^3.17.3"
},
diff --git a/apps/example-todo-app/src/app/edge/route.ts b/apps/example-todo-app/src/app/edge/route.ts
new file mode 100644
index 000000000..fa91fade9
--- /dev/null
+++ b/apps/example-todo-app/src/app/edge/route.ts
@@ -0,0 +1,15 @@
+import { withRouteSpecEdge } from "@/lib/middlewares"
+import { z } from "zod"
+
+export const runtime = "edge"
+
+const route_spec = {
+ jsonResponse: z.object({
+ return: z.boolean(),
+ }),
+ auth: "none",
+} as const
+
+export const GET = withRouteSpecEdge(route_spec)((req) => {
+ return req.responseEdge.status(200).json({ return: true })
+})
diff --git a/apps/example-todo-app/lib/middlewares/index.ts b/apps/example-todo-app/src/lib/middlewares/index.ts
similarity index 100%
rename from apps/example-todo-app/lib/middlewares/index.ts
rename to apps/example-todo-app/src/lib/middlewares/index.ts
diff --git a/apps/example-todo-app/src/lib/middlewares/with-auth-token-edge.ts b/apps/example-todo-app/src/lib/middlewares/with-auth-token-edge.ts
new file mode 100644
index 000000000..8e4674126
--- /dev/null
+++ b/apps/example-todo-app/src/lib/middlewares/with-auth-token-edge.ts
@@ -0,0 +1,24 @@
+import { UnauthorizedException, MiddlewareEdge } from "nextlove"
+
+export const withAuthTokenEdge: MiddlewareEdge<{
+ auth: {
+ authorized_by: "auth_token"
+ }
+}> = (next) => async (req) => {
+ const authorization = req.headers.get("authorization")
+
+ if (authorization?.split("Bearer ")?.[1] !== "auth_token") {
+ throw new UnauthorizedException({
+ type: "unauthorized",
+ message: "Unauthorized",
+ })
+ }
+
+ req.auth = {
+ authorized_by: "auth_token",
+ }
+
+ return next(req)
+}
+
+export default withAuthTokenEdge
diff --git a/apps/example-todo-app/lib/middlewares/with-auth-token.ts b/apps/example-todo-app/src/lib/middlewares/with-auth-token.ts
similarity index 82%
rename from apps/example-todo-app/lib/middlewares/with-auth-token.ts
rename to apps/example-todo-app/src/lib/middlewares/with-auth-token.ts
index 31e89cc09..e511782a5 100644
--- a/apps/example-todo-app/lib/middlewares/with-auth-token.ts
+++ b/apps/example-todo-app/src/lib/middlewares/with-auth-token.ts
@@ -19,10 +19,4 @@ export const withAuthToken: Middleware<{
return next(req, res)
}
-withAuthToken.securitySchema = {
- type: "http",
- scheme: "bearer",
- bearerFormat: "API Token",
-}
-
export default withAuthToken
diff --git a/apps/example-todo-app/lib/middlewares/with-route-spec.ts b/apps/example-todo-app/src/lib/middlewares/with-route-spec.ts
similarity index 67%
rename from apps/example-todo-app/lib/middlewares/with-route-spec.ts
rename to apps/example-todo-app/src/lib/middlewares/with-route-spec.ts
index 8e40883dc..0e048086b 100644
--- a/apps/example-todo-app/lib/middlewares/with-route-spec.ts
+++ b/apps/example-todo-app/src/lib/middlewares/with-route-spec.ts
@@ -1,5 +1,6 @@
-import { createWithRouteSpec } from "nextlove"
+import { createWithRouteSpec, createWithRouteSpecEdge } from "nextlove"
import { withAuthToken } from "./with-auth-token"
+import withAuthTokenEdge from "./with-auth-token-edge"
export { checkRouteSpec } from "nextlove"
export const withRouteSpec = createWithRouteSpec({
@@ -10,6 +11,16 @@ export const withRouteSpec = createWithRouteSpec({
shouldValidateResponses: true,
} as const)
+export const withRouteSpecEdge = createWithRouteSpecEdge({
+ authMiddlewareMap: { auth_token: withAuthTokenEdge },
+ globalMiddlewares: [],
+ apiName: "TODO API",
+ productionServerUrl: "https://example.com",
+ shouldValidateResponses: true,
+ addOkStatus: true,
+} as const)
+
+
export const withRouteSpecWithoutValidateGetRequestBody = createWithRouteSpec({
authMiddlewareMap: { auth_token: withAuthToken },
globalMiddlewares: [],
diff --git a/apps/example-todo-app/pages/api/health.ts b/apps/example-todo-app/src/pages/api/health.ts
similarity index 100%
rename from apps/example-todo-app/pages/api/health.ts
rename to apps/example-todo-app/src/pages/api/health.ts
diff --git a/apps/example-todo-app/pages/api/todo/add-ignore-invalid-json-response.ts b/apps/example-todo-app/src/pages/api/todo/add-ignore-invalid-json-response.ts
similarity index 95%
rename from apps/example-todo-app/pages/api/todo/add-ignore-invalid-json-response.ts
rename to apps/example-todo-app/src/pages/api/todo/add-ignore-invalid-json-response.ts
index 3735c79c2..b98d1f0d7 100644
--- a/apps/example-todo-app/pages/api/todo/add-ignore-invalid-json-response.ts
+++ b/apps/example-todo-app/src/pages/api/todo/add-ignore-invalid-json-response.ts
@@ -1,7 +1,7 @@
import {
withRouteSpecWithoutValidateResponse,
checkRouteSpec,
-} from "lib/middlewares"
+} from "@/lib/middlewares"
import { z } from "zod"
import { v4 as uuidv4 } from "uuid"
diff --git a/apps/example-todo-app/pages/api/todo/add-invalid-json-response.ts b/apps/example-todo-app/src/pages/api/todo/add-invalid-json-response.ts
similarity index 91%
rename from apps/example-todo-app/pages/api/todo/add-invalid-json-response.ts
rename to apps/example-todo-app/src/pages/api/todo/add-invalid-json-response.ts
index 18c5eb5a1..a615ac1cf 100644
--- a/apps/example-todo-app/pages/api/todo/add-invalid-json-response.ts
+++ b/apps/example-todo-app/src/pages/api/todo/add-invalid-json-response.ts
@@ -1,4 +1,4 @@
-import { withRouteSpec } from "lib/middlewares"
+import { withRouteSpec } from "@/lib/middlewares"
import { z } from "zod"
import { v4 as uuidv4 } from "uuid"
diff --git a/apps/example-todo-app/pages/api/todo/add.ts b/apps/example-todo-app/src/pages/api/todo/add.ts
similarity index 80%
rename from apps/example-todo-app/pages/api/todo/add.ts
rename to apps/example-todo-app/src/pages/api/todo/add.ts
index da44443d3..69c3d4900 100644
--- a/apps/example-todo-app/pages/api/todo/add.ts
+++ b/apps/example-todo-app/src/pages/api/todo/add.ts
@@ -1,4 +1,4 @@
-import { withRouteSpec, checkRouteSpec } from "lib/middlewares"
+import { withRouteSpec } from "@/lib/middlewares"
import { z } from "zod"
import { v4 as uuidv4 } from "uuid"
@@ -8,14 +8,14 @@ export const jsonBody = z.object({
completed: z.boolean().optional().default(false),
})
-export const route_spec = checkRouteSpec({
+export const route_spec = {
methods: ["POST"],
auth: "auth_token",
jsonBody,
jsonResponse: z.object({
ok: z.boolean(),
}),
-})
+} as const
export default withRouteSpec(route_spec)(async (req, res) => {
return res.status(200).json({ ok: true })
diff --git a/apps/example-todo-app/pages/api/todo/delete-common-params.ts b/apps/example-todo-app/src/pages/api/todo/delete-common-params.ts
similarity index 90%
rename from apps/example-todo-app/pages/api/todo/delete-common-params.ts
rename to apps/example-todo-app/src/pages/api/todo/delete-common-params.ts
index 5ca5fe286..02f273350 100644
--- a/apps/example-todo-app/pages/api/todo/delete-common-params.ts
+++ b/apps/example-todo-app/src/pages/api/todo/delete-common-params.ts
@@ -1,4 +1,4 @@
-import { checkRouteSpec, withRouteSpec } from "lib/middlewares"
+import { checkRouteSpec, withRouteSpec } from "@/lib/middlewares"
import { NotFoundException } from "nextlove"
import { TODO_ID } from "tests/fixtures"
import { z } from "zod"
diff --git a/apps/example-todo-app/pages/api/todo/delete.ts b/apps/example-todo-app/src/pages/api/todo/delete.ts
similarity index 90%
rename from apps/example-todo-app/pages/api/todo/delete.ts
rename to apps/example-todo-app/src/pages/api/todo/delete.ts
index 85ee01b81..8725d49a6 100644
--- a/apps/example-todo-app/pages/api/todo/delete.ts
+++ b/apps/example-todo-app/src/pages/api/todo/delete.ts
@@ -1,4 +1,4 @@
-import { checkRouteSpec, withRouteSpec } from "lib/middlewares"
+import { checkRouteSpec, withRouteSpec } from "@/lib/middlewares"
import { NotFoundException } from "nextlove"
import { TODO_ID } from "tests/fixtures"
import { z } from "zod"
diff --git a/apps/example-todo-app/pages/api/todo/form-add.ts b/apps/example-todo-app/src/pages/api/todo/form-add.ts
similarity index 91%
rename from apps/example-todo-app/pages/api/todo/form-add.ts
rename to apps/example-todo-app/src/pages/api/todo/form-add.ts
index 86f70bede..a3624f8c5 100644
--- a/apps/example-todo-app/pages/api/todo/form-add.ts
+++ b/apps/example-todo-app/src/pages/api/todo/form-add.ts
@@ -1,4 +1,4 @@
-import { withRouteSpec, checkRouteSpec } from "lib/middlewares"
+import { withRouteSpec, checkRouteSpec } from "@/lib/middlewares"
import { z } from "zod"
import { v4 as uuidv4 } from "uuid"
import { HttpException } from "nextlove"
diff --git a/apps/example-todo-app/pages/api/todo/get-no-validate-body.ts b/apps/example-todo-app/src/pages/api/todo/get-no-validate-body.ts
similarity index 95%
rename from apps/example-todo-app/pages/api/todo/get-no-validate-body.ts
rename to apps/example-todo-app/src/pages/api/todo/get-no-validate-body.ts
index 83a393caf..48688ea7d 100644
--- a/apps/example-todo-app/pages/api/todo/get-no-validate-body.ts
+++ b/apps/example-todo-app/src/pages/api/todo/get-no-validate-body.ts
@@ -1,7 +1,7 @@
import {
checkRouteSpec,
withRouteSpecWithoutValidateGetRequestBody,
-} from "lib/middlewares"
+} from "@/lib/middlewares"
import { NotFoundException } from "nextlove"
import { TODO_ID } from "tests/fixtures"
import { z } from "zod"
diff --git a/apps/example-todo-app/pages/api/todo/get.ts b/apps/example-todo-app/src/pages/api/todo/get.ts
similarity index 93%
rename from apps/example-todo-app/pages/api/todo/get.ts
rename to apps/example-todo-app/src/pages/api/todo/get.ts
index 24daf19a6..114dcf4a9 100644
--- a/apps/example-todo-app/pages/api/todo/get.ts
+++ b/apps/example-todo-app/src/pages/api/todo/get.ts
@@ -1,4 +1,4 @@
-import { checkRouteSpec, withRouteSpec } from "lib/middlewares"
+import { checkRouteSpec, withRouteSpec } from "@/lib/middlewares"
import { NotFoundException } from "nextlove"
import { TODO_ID } from "tests/fixtures"
import { z } from "zod"
@@ -20,7 +20,7 @@ export const route_spec = checkRouteSpec({
ok: z.boolean(),
todo: z.object({
id: z.string().uuid(),
- }),
+ }).optional(),
error: z
.object({
type: z.string(),
diff --git a/apps/example-todo-app/pages/api/todo/index.ts b/apps/example-todo-app/src/pages/api/todo/index.ts
similarity index 93%
rename from apps/example-todo-app/pages/api/todo/index.ts
rename to apps/example-todo-app/src/pages/api/todo/index.ts
index 24daf19a6..114dcf4a9 100644
--- a/apps/example-todo-app/pages/api/todo/index.ts
+++ b/apps/example-todo-app/src/pages/api/todo/index.ts
@@ -1,4 +1,4 @@
-import { checkRouteSpec, withRouteSpec } from "lib/middlewares"
+import { checkRouteSpec, withRouteSpec } from "@/lib/middlewares"
import { NotFoundException } from "nextlove"
import { TODO_ID } from "tests/fixtures"
import { z } from "zod"
@@ -20,7 +20,7 @@ export const route_spec = checkRouteSpec({
ok: z.boolean(),
todo: z.object({
id: z.string().uuid(),
- }),
+ }).optional(),
error: z
.object({
type: z.string(),
diff --git a/apps/example-todo-app/pages/api/todo/json-response-must-be-schema.ts b/apps/example-todo-app/src/pages/api/todo/json-response-must-be-schema.ts
similarity index 90%
rename from apps/example-todo-app/pages/api/todo/json-response-must-be-schema.ts
rename to apps/example-todo-app/src/pages/api/todo/json-response-must-be-schema.ts
index 5dbb80023..cec70a6e9 100644
--- a/apps/example-todo-app/pages/api/todo/json-response-must-be-schema.ts
+++ b/apps/example-todo-app/src/pages/api/todo/json-response-must-be-schema.ts
@@ -1,4 +1,4 @@
-import { withRouteSpec } from "lib/middlewares"
+import { withRouteSpec } from "@/lib/middlewares"
import { z } from "zod"
import { v4 as uuidv4 } from "uuid"
diff --git a/apps/example-todo-app/pages/api/todo/list-optional-ids.ts b/apps/example-todo-app/src/pages/api/todo/list-optional-ids.ts
similarity index 89%
rename from apps/example-todo-app/pages/api/todo/list-optional-ids.ts
rename to apps/example-todo-app/src/pages/api/todo/list-optional-ids.ts
index 1ce757027..8df769953 100644
--- a/apps/example-todo-app/pages/api/todo/list-optional-ids.ts
+++ b/apps/example-todo-app/src/pages/api/todo/list-optional-ids.ts
@@ -1,4 +1,4 @@
-import { checkRouteSpec, withRouteSpec } from "lib/middlewares"
+import { checkRouteSpec, withRouteSpec } from "@/lib/middlewares"
import { z } from "zod"
export const commonParams = z.object({
diff --git a/apps/example-todo-app/pages/api/todo/list-with-refine.ts b/apps/example-todo-app/src/pages/api/todo/list-with-refine.ts
similarity index 94%
rename from apps/example-todo-app/pages/api/todo/list-with-refine.ts
rename to apps/example-todo-app/src/pages/api/todo/list-with-refine.ts
index 30fb9aa37..9334f9a2c 100644
--- a/apps/example-todo-app/pages/api/todo/list-with-refine.ts
+++ b/apps/example-todo-app/src/pages/api/todo/list-with-refine.ts
@@ -1,4 +1,4 @@
-import { checkRouteSpec, withRouteSpec } from "lib/middlewares"
+import { checkRouteSpec, withRouteSpec } from "@/lib/middlewares"
import { z } from "zod"
export const commonParams = z
diff --git a/apps/example-todo-app/pages/api/todo/list.ts b/apps/example-todo-app/src/pages/api/todo/list.ts
similarity index 88%
rename from apps/example-todo-app/pages/api/todo/list.ts
rename to apps/example-todo-app/src/pages/api/todo/list.ts
index bf0126557..9878aa511 100644
--- a/apps/example-todo-app/pages/api/todo/list.ts
+++ b/apps/example-todo-app/src/pages/api/todo/list.ts
@@ -1,4 +1,4 @@
-import { checkRouteSpec, withRouteSpec } from "lib/middlewares"
+import { checkRouteSpec, withRouteSpec } from "@/lib/middlewares"
import { z } from "zod"
export const commonParams = z.object({
diff --git a/apps/example-todo-app/tests/api/health.test.ts b/apps/example-todo-app/tests/api/health.test.ts
index 7e6d3647a..cb4532272 100644
--- a/apps/example-todo-app/tests/api/health.test.ts
+++ b/apps/example-todo-app/tests/api/health.test.ts
@@ -1,9 +1,9 @@
import test from "ava"
-import getTestServer from "tests/fixtures/get-test-server"
+import getTestServer from "../fixtures/get-test-server"
-test("GET /health", async (t) => {
+test("GET /api/health", async (t) => {
const { axios } = await getTestServer(t)
- const res = await axios.get("/health")
+ const res = await axios.get("/api/health")
t.truthy(res.data.ok)
})
diff --git a/apps/example-todo-app/tests/api/todo/add-ignore-invalid-json-response.test.ts b/apps/example-todo-app/tests/api/todo/add-ignore-invalid-json-response.test.ts
index 68dc44699..d22d683a4 100644
--- a/apps/example-todo-app/tests/api/todo/add-ignore-invalid-json-response.test.ts
+++ b/apps/example-todo-app/tests/api/todo/add-ignore-invalid-json-response.test.ts
@@ -7,7 +7,7 @@ test("POST /todo/add-ignore-invalid-json-response", async (t) => {
axios.defaults.headers.common.Authorization = `Bearer auth_token`
const successfulRes = await axios
- .post("/todo/add-ignore-invalid-json-response", { title: "Todo Title" })
+ .post("/api/todo/add-ignore-invalid-json-response", { title: "Todo Title" })
.catch((err) => err)
t.is(successfulRes.status, 200)
diff --git a/apps/example-todo-app/tests/api/todo/add-invalid-json-response.test.ts b/apps/example-todo-app/tests/api/todo/add-invalid-json-response.test.ts
index 6136e2246..ac8cebc06 100644
--- a/apps/example-todo-app/tests/api/todo/add-invalid-json-response.test.ts
+++ b/apps/example-todo-app/tests/api/todo/add-invalid-json-response.test.ts
@@ -7,7 +7,7 @@ test("POST /todo/add-invalid-json-response", async (t) => {
axios.defaults.headers.common.Authorization = `Bearer auth_token`
const successfulRes = await axios
- .post("/todo/add-invalid-json-response", { title: "Todo Title" })
+ .post("/api/todo/add-invalid-json-response", { title: "Todo Title" })
.catch((err) => err)
t.is(successfulRes.status, 500)
diff --git a/apps/example-todo-app/tests/api/todo/add.test.ts b/apps/example-todo-app/tests/api/todo/add.test.ts
index beaa8ba5d..ca6104154 100644
--- a/apps/example-todo-app/tests/api/todo/add.test.ts
+++ b/apps/example-todo-app/tests/api/todo/add.test.ts
@@ -4,7 +4,7 @@ import getTestServer from "tests/fixtures/get-test-server"
test("POST /todo/add", async (t) => {
const { axios } = await getTestServer(t)
- const noAuthRes = await axios.post("/todo/add").catch((err) => err)
+ const noAuthRes = await axios.post("/api/todo/add").catch((err) => err)
t.is(noAuthRes.status, 401, "no auth")
const hasErrorStack = Boolean(noAuthRes.response.error.stack)
@@ -12,21 +12,21 @@ test("POST /todo/add", async (t) => {
axios.defaults.headers.common.Authorization = `Bearer auth_token`
- const invalidMethodRes = await axios.get("/todo/add").catch((err) => err)
+ const invalidMethodRes = await axios.get("/api/todo/add").catch((err) => err)
t.is(invalidMethodRes.status, 405, "invalid method")
const invalidBodyParamTypeRes = await axios
- .post("/todo/add", { title: true })
+ .post("/api/todo/add", { title: true })
.catch((err) => err)
t.is(invalidBodyParamTypeRes.status, 400, "bad body")
const nonExistentIdRes = await axios
- .post("/todo/add", { invalidParam: "invalidParam" })
+ .post("/api/todo/add", { invalidParam: "invalidParam" })
.catch((err) => err)
t.is(nonExistentIdRes.status, 400, "invalid param")
const successfulRes = await axios
- .post("/todo/add", { title: "Todo Title" })
+ .post("/api/todo/add", { title: "Todo Title" })
.catch((err) => err)
t.is(successfulRes.status, 200)
})
diff --git a/apps/example-todo-app/tests/api/todo/delete-common-params.test.ts b/apps/example-todo-app/tests/api/todo/delete-common-params.test.ts
index 873fdf592..e4dab809e 100644
--- a/apps/example-todo-app/tests/api/todo/delete-common-params.test.ts
+++ b/apps/example-todo-app/tests/api/todo/delete-common-params.test.ts
@@ -7,34 +7,34 @@ test("DELETE /todo/delete-common-params", async (t) => {
const { axios } = await getTestServer(t)
const noAuthRes = await axios
- .delete("/todo/delete-common-params")
+ .delete("/api/todo/delete-common-params")
.catch((err) => err)
t.is(noAuthRes.status, 401, "no auth")
axios.defaults.headers.common.Authorization = `Bearer auth_token`
const invalidMethodRes = await axios
- .get("/todo/delete-common-params")
+ .get("/api/todo/delete-common-params")
.catch((err) => err)
t.is(invalidMethodRes.status, 405, "invalid method")
const invalidIdFormatRes = await axios
- .delete("/todo/delete-common-params", { data: { id: "someId" } })
+ .delete("/api/todo/delete-common-params", { data: { id: "someId" } })
.catch((err) => err)
t.is(invalidIdFormatRes.status, 400, "invalid id format")
const invalidIdTypeRes = await axios
- .delete("/todo/delete-common-params", { data: { id: 123 } })
+ .delete("/api/todo/delete-common-params", { data: { id: 123 } })
.catch((err) => err)
t.is(invalidIdTypeRes.status, 400, "invalid id type")
const nonExistentIdRes = await axios
- .delete("/todo/delete-common-params", { data: { id: uuidv4() } })
+ .delete("/api/todo/delete-common-params", { data: { id: uuidv4() } })
.catch((err) => err)
t.is(nonExistentIdRes.status, 404, "non-existent id")
const successfulRes = await axios
- .delete("/todo/delete-common-params", { data: { id: TODO_ID } })
+ .delete("/api/todo/delete-common-params", { data: { id: TODO_ID } })
.catch((err) => err)
t.is(successfulRes.status, 200)
})
diff --git a/apps/example-todo-app/tests/api/todo/delete.test.ts b/apps/example-todo-app/tests/api/todo/delete.test.ts
index 75c47977e..ee90cf34b 100644
--- a/apps/example-todo-app/tests/api/todo/delete.test.ts
+++ b/apps/example-todo-app/tests/api/todo/delete.test.ts
@@ -6,31 +6,31 @@ import { v4 as uuidv4 } from "uuid"
test("DELETE /todo/delete", async (t) => {
const { axios } = await getTestServer(t)
- const noAuthRes = await axios.delete("/todo/delete").catch((err) => err)
+ const noAuthRes = await axios.delete("/api/todo/delete").catch((err) => err)
t.is(noAuthRes.status, 401, "no auth")
axios.defaults.headers.common.Authorization = `Bearer auth_token`
- const invalidMethodRes = await axios.get("/todo/delete").catch((err) => err)
+ const invalidMethodRes = await axios.get("/api/todo/delete").catch((err) => err)
t.is(invalidMethodRes.status, 405, "invalid method")
const invalidIdFormatRes = await axios
- .delete("/todo/delete", { data: { id: "someId" } })
+ .delete("/api/todo/delete", { data: { id: "someId" } })
.catch((err) => err)
t.is(invalidIdFormatRes.status, 400, "invalid id format")
const invalidIdTypeRes = await axios
- .delete("/todo/delete", { data: { id: 123 } })
+ .delete("/api/todo/delete", { data: { id: 123 } })
.catch((err) => err)
t.is(invalidIdTypeRes.status, 400, "invalid id type")
const nonExistentIdRes = await axios
- .delete("/todo/delete", { data: { id: uuidv4() } })
+ .delete("/api/todo/delete", { data: { id: uuidv4() } })
.catch((err) => err)
t.is(nonExistentIdRes.status, 404, "non-existent id")
const successfulRes = await axios
- .delete("/todo/delete", { data: { id: TODO_ID } })
+ .delete("/api/todo/delete", { data: { id: TODO_ID } })
.catch((err) => err)
t.is(successfulRes.status, 200)
})
diff --git a/apps/example-todo-app/tests/api/todo/form-add.test.ts b/apps/example-todo-app/tests/api/todo/form-add.test.ts
index 7bc084e13..58be8c5f8 100644
--- a/apps/example-todo-app/tests/api/todo/form-add.test.ts
+++ b/apps/example-todo-app/tests/api/todo/form-add.test.ts
@@ -2,7 +2,7 @@ import test from "ava"
import { TODO_ID } from "tests/fixtures"
import getTestServer from "tests/fixtures/get-test-server"
import { v4 as uuidv4 } from "uuid"
-import { formData } from "pages/api/todo/form-add"
+import { formData } from "@/pages/api/todo/form-add"
test("POST /todo/form-add", async (t) => {
const { axios } = await getTestServer(t)
@@ -14,7 +14,7 @@ test("POST /todo/form-add", async (t) => {
const successfulRes = await axios({
method: "POST",
- url: "/todo/form-add",
+ url: "/api/todo/form-add",
data: bodyFormData,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
diff --git a/apps/example-todo-app/tests/api/todo/get-boolean.test.ts b/apps/example-todo-app/tests/api/todo/get-boolean.test.ts
index 977bfc8bd..912d89e1d 100644
--- a/apps/example-todo-app/tests/api/todo/get-boolean.test.ts
+++ b/apps/example-todo-app/tests/api/todo/get-boolean.test.ts
@@ -4,7 +4,7 @@ import axiosAssert from "tests/fixtures/axios-assert"
import getTestServer from "tests/fixtures/get-test-server"
import { v4 as uuidv4 } from "uuid"
-test.failing("GET /todo/get", async (t) => {
+test("GET /todo/get", async (t) => {
const { axios } = await getTestServer(t)
axios.defaults.headers.common.Authorization = `Bearer auth_token`
@@ -12,7 +12,7 @@ test.failing("GET /todo/get", async (t) => {
const id = uuidv4()
const invalidIdFormatRes = await axios
- .get(`/todo/get?id=${id}&throwError=false`)
+ .get(`/api/todo/get?id=${id}&throwError=false`)
.catch((err) => err)
t.is(invalidIdFormatRes.status, 200)
@@ -23,7 +23,7 @@ test.failing("GET /todo/get", async (t) => {
axiosAssert.throws(
t,
- () => axios.get(`/todo/get?id=${id}&throwErrorAlwaysTrue=false`),
+ () => axios.get(`/api/todo/get?id=${id}&throwErrorAlwaysTrue=false`),
{
error: {
type: "invalid_input",
diff --git a/apps/example-todo-app/tests/api/todo/get-no-validate-body.test.ts b/apps/example-todo-app/tests/api/todo/get-no-validate-body.test.ts
index 3d2a6e86a..379c8c962 100644
--- a/apps/example-todo-app/tests/api/todo/get-no-validate-body.test.ts
+++ b/apps/example-todo-app/tests/api/todo/get-no-validate-body.test.ts
@@ -6,6 +6,6 @@ import { v4 as uuidv4 } from "uuid"
test("GET /todo/get", async (t) => {
const { axios } = await getTestServer(t)
axios.defaults.headers.common.Authorization = `Bearer auth_token`
- const successfulRes = await axios.get(`/todo/get-no-validate-body`)
+ const successfulRes = await axios.get(`/api/todo/get-no-validate-body`)
t.is(successfulRes.status, 200)
})
diff --git a/apps/example-todo-app/tests/api/todo/get.test.ts b/apps/example-todo-app/tests/api/todo/get.test.ts
index faed13dfb..363330e29 100644
--- a/apps/example-todo-app/tests/api/todo/get.test.ts
+++ b/apps/example-todo-app/tests/api/todo/get.test.ts
@@ -6,27 +6,27 @@ import { v4 as uuidv4 } from "uuid"
test("GET /todo/get", async (t) => {
const { axios } = await getTestServer(t)
- const noAuthRes = await axios.get("/todo/get").catch((err) => err)
+ const noAuthRes = await axios.get("/api/todo/get").catch((err) => err)
t.is(noAuthRes.status, 401)
axios.defaults.headers.common.Authorization = `Bearer auth_token`
- const invalidMethodRes = await axios.post("/todo/get").catch((err) => err)
+ const invalidMethodRes = await axios.post("/api/todo/get").catch((err) => err)
t.is(invalidMethodRes.status, 405)
const invalidIdFormatRes = await axios
- .get("/todo/get?id=someId")
+ .get("/api/todo/get?id=someId")
.catch((err) => err)
t.is(invalidIdFormatRes.status, 400)
const nonExistentIdRes = await axios
- .get(`/todo/get?id=${uuidv4()}`)
+ .get(`/api/todo/get?id=${uuidv4()}`)
.catch((err) => err)
t.is(nonExistentIdRes.status, 404)
// Test 200 response
const successfulRes = await axios
- .get(`/todo/get?id=${TODO_ID}`)
+ .get(`/api/todo/get?id=${TODO_ID}`)
.catch((err) => err)
t.is(successfulRes.status, 200)
})
diff --git a/apps/example-todo-app/tests/api/todo/list-optional-ids.test.ts b/apps/example-todo-app/tests/api/todo/list-optional-ids.test.ts
index a498e6105..d85682381 100644
--- a/apps/example-todo-app/tests/api/todo/list-optional-ids.test.ts
+++ b/apps/example-todo-app/tests/api/todo/list-optional-ids.test.ts
@@ -9,7 +9,7 @@ test("GET /todo/list-optional-ids", async (t) => {
const ids = [uuidv4(), uuidv4()]
- const responseWithArray = await axios.get("/todo/list-optional-ids", {
+ const responseWithArray = await axios.get("/api/todo/list-optional-ids", {
params: {
ids,
},
@@ -22,7 +22,7 @@ test("GET /todo/list-optional-ids", async (t) => {
})),
})
- const responseWithCommas = await axios.get("/todo/list-optional-ids", {
+ const responseWithCommas = await axios.get("/api/todo/list-optional-ids", {
params: {
ids: ids.join(","),
},
@@ -35,7 +35,7 @@ test("GET /todo/list-optional-ids", async (t) => {
})),
})
- const responseWithOptionalIds = await axios.get("/todo/list-optional-ids")
+ const responseWithOptionalIds = await axios.get("/api/todo/list-optional-ids")
t.deepEqual(responseWithOptionalIds.data, {
ok: true,
diff --git a/apps/example-todo-app/tests/api/todo/list-with-refine.test.ts b/apps/example-todo-app/tests/api/todo/list-with-refine.test.ts
index c9099b0ae..331c61060 100644
--- a/apps/example-todo-app/tests/api/todo/list-with-refine.test.ts
+++ b/apps/example-todo-app/tests/api/todo/list-with-refine.test.ts
@@ -10,7 +10,7 @@ test("GET /todo/list-with-refine", async (t) => {
const ids = [uuidv4(), uuidv4()]
- const responseWithArray = await axios.get("/todo/list-with-refine", {
+ const responseWithArray = await axios.get("/api/todo/list-with-refine", {
params: {
ids,
},
@@ -23,7 +23,7 @@ test("GET /todo/list-with-refine", async (t) => {
})),
})
- const responseWithCommas = await axios.get("/todo/list-with-refine", {
+ const responseWithCommas = await axios.get("/api/todo/list-with-refine", {
params: {
ids: ids.join(","),
},
@@ -37,7 +37,7 @@ test("GET /todo/list-with-refine", async (t) => {
})
const title = uuidv4()
- const responseWithTitle = await axios.get("/todo/list-with-refine", {
+ const responseWithTitle = await axios.get("/api/todo/list-with-refine", {
params: {
title,
},
@@ -52,7 +52,7 @@ test("GET /todo/list-with-refine", async (t) => {
],
})
- await axiosAssert.throws(t, async () => axios.get("/todo/list-with-refine"), {
+ await axiosAssert.throws(t, async () => axios.get("/api/todo/list-with-refine"), {
status: 400,
error: {
type: "invalid_input",
@@ -63,7 +63,7 @@ test("GET /todo/list-with-refine", async (t) => {
await axiosAssert.throws(
t,
async () =>
- axios.get("/todo/list-with-refine", {
+ axios.get("/api/todo/list-with-refine", {
params: {
title: "title",
ids: ids.join(","),
@@ -81,7 +81,7 @@ test("GET /todo/list-with-refine", async (t) => {
await axiosAssert.throws(
t,
async () =>
- axios.get("/todo/list-with-refine", {
+ axios.get("/api/todo/list-with-refine", {
params: {
title:
"A title big enough to test if nextlove is handling correct with nested .refine (from zod) with at least 101 characters long",
diff --git a/apps/example-todo-app/tests/api/todo/list.test.ts b/apps/example-todo-app/tests/api/todo/list.test.ts
index 2677a727e..6c0c9ad9c 100644
--- a/apps/example-todo-app/tests/api/todo/list.test.ts
+++ b/apps/example-todo-app/tests/api/todo/list.test.ts
@@ -9,7 +9,7 @@ test("GET /todo/list", async (t) => {
const ids = [uuidv4(), uuidv4()]
- const responseWithArray = await axios.get("/todo/list", {
+ const responseWithArray = await axios.get("/api/todo/list", {
params: {
ids,
},
@@ -22,7 +22,7 @@ test("GET /todo/list", async (t) => {
})),
})
- const responseWithCommas = await axios.get("/todo/list", {
+ const responseWithCommas = await axios.get("/api/todo/list", {
params: {
ids: ids.join(","),
},
diff --git a/apps/example-todo-app/tsconfig.json b/apps/example-todo-app/tsconfig.json
index 23e023584..717b988ff 100755
--- a/apps/example-todo-app/tsconfig.json
+++ b/apps/example-todo-app/tsconfig.json
@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "es5",
- "lib": ["dom", "dom.iterable", "esnext"],
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
@@ -14,8 +18,29 @@
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
- "baseUrl": "."
+ "strictNullChecks": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": [
+ "./src/*"
+ ],
+ "tests": [
+ "./tests/*"
+ ]
+ },
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
},
- "include": ["next-env.d.ts", "**/*.ts"],
- "exclude": ["node_modules"]
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
}
diff --git a/packages/nextlove/src/generate-openapi/index.ts b/packages/nextlove/nextlove-generate/generate-openapi/index.ts
similarity index 91%
rename from packages/nextlove/src/generate-openapi/index.ts
rename to packages/nextlove/nextlove-generate/generate-openapi/index.ts
index 1345d4442..eb8d45f15 100644
--- a/packages/nextlove/src/generate-openapi/index.ts
+++ b/packages/nextlove/nextlove-generate/generate-openapi/index.ts
@@ -1,7 +1,7 @@
import fs from "node:fs/promises"
import { generateSchema } from "@anatine/zod-openapi"
import { OpenApiBuilder, OperationObject, ParameterObject } from "openapi3-ts"
-import { SetupParams } from "../types"
+import { SetupParams } from "../../src/types"
import chalk from "chalk"
import { z } from "zod"
import { parseRoutesInPackage } from "../lib/parse-routes-in-package"
@@ -85,15 +85,16 @@ export async function generateOpenAPI(opts: GenerateOpenAPIOpts) {
const securityObjectsForAuthType = {}
for (const authName of Object.keys(globalSetupParams.authMiddlewareMap)) {
const mw = globalSetupParams.authMiddlewareMap[authName]
- if (mw.securitySchema) {
- securitySchemes[authName] = (mw as any).securitySchema
- } else {
- console.warn(
- chalk.yellow(
- `Authentication middleware "${authName}" has no securitySchema. You can define this on the function (e.g. after the export do... \n\nmyMiddleware.securitySchema = {\n type: "http"\n scheme: "bearer"\n bearerFormat: "JWT"\n // or API Token etc.\n}\n\nYou can also define "securityObjects" this way, if you want to make the endpoint support multiple modes of authentication.\n\n`
- )
- )
- }
+ // TODO: remove this warning
+ // if (mw.securitySchema) {
+ // securitySchemes[authName] = (mw as any).securitySchema
+ // } else {
+ // console.warn(
+ // chalk.yellow(
+ // `Authentication middleware "${authName}" has no securitySchema. You can define this on the function (e.g. after the export do... \n\nmyMiddleware.securitySchema = {\n type: "http"\n scheme: "bearer"\n bearerFormat: "JWT"\n // or API Token etc.\n}\n\nYou can also define "securityObjects" this way, if you want to make the endpoint support multiple modes of authentication.\n\n`
+ // )
+ // )
+ // }
securityObjectsForAuthType[authName] = (mw as any).securityObjects || [
{
diff --git a/packages/nextlove/src/generate-route-types/index.ts b/packages/nextlove/nextlove-generate/generate-route-types/index.ts
similarity index 96%
rename from packages/nextlove/src/generate-route-types/index.ts
rename to packages/nextlove/nextlove-generate/generate-route-types/index.ts
index b4988c688..8aef8c270 100644
--- a/packages/nextlove/src/generate-route-types/index.ts
+++ b/packages/nextlove/nextlove-generate/generate-route-types/index.ts
@@ -1,5 +1,4 @@
import * as fs from "node:fs/promises"
-import { defaultMapFilePathToHTTPRoute } from "../lib/default-map-file-path-to-http-route"
import { parseRoutesInPackage } from "../lib/parse-routes-in-package"
import { zodToTs, printNode } from "zod-to-ts"
import prettier from "prettier"
diff --git a/packages/nextlove/src/lib/default-map-file-path-to-http-route.ts b/packages/nextlove/nextlove-generate/lib/default-map-file-path-to-http-route.ts
similarity index 100%
rename from packages/nextlove/src/lib/default-map-file-path-to-http-route.ts
rename to packages/nextlove/nextlove-generate/lib/default-map-file-path-to-http-route.ts
diff --git a/packages/nextlove/src/lib/parse-routes-in-package.ts b/packages/nextlove/nextlove-generate/lib/parse-routes-in-package.ts
similarity index 96%
rename from packages/nextlove/src/lib/parse-routes-in-package.ts
rename to packages/nextlove/nextlove-generate/lib/parse-routes-in-package.ts
index efd45dce5..3af20f191 100644
--- a/packages/nextlove/src/lib/parse-routes-in-package.ts
+++ b/packages/nextlove/nextlove-generate/lib/parse-routes-in-package.ts
@@ -1,7 +1,7 @@
import chalk from "chalk"
import path from "node:path"
import globby from "globby"
-import { RouteSpec, SetupParams } from "../types"
+import { RouteSpec, SetupParams } from "../../src/types"
import { defaultMapFilePathToHTTPRoute } from "./default-map-file-path-to-http-route"
export interface RouteInfo {
diff --git a/packages/nextlove/package.json b/packages/nextlove/package.json
index 10c75c07c..07074af44 100644
--- a/packages/nextlove/package.json
+++ b/packages/nextlove/package.json
@@ -18,6 +18,7 @@
"scripts": {
"test": "npm run typecheck",
"typecheck": "tsc --noEmit",
+ "dev": "tsup --dts --sourcemap inline src --watch",
"build": "tsup --dts --sourcemap inline src",
"yalc": "npm run build && yalc push"
},
diff --git a/packages/nextlove/src/edge-helpers.ts b/packages/nextlove/src/edge-helpers.ts
new file mode 100644
index 000000000..f98cff22d
--- /dev/null
+++ b/packages/nextlove/src/edge-helpers.ts
@@ -0,0 +1,46 @@
+import { NextRequest, NextResponse } from "next/server"
+
+export type NextloveResponse = ReturnType
+export type NextloveRequest = NextRequest & {
+ responseEdge: NextloveResponse
+}
+
+const DEFAULT_STATUS = 200
+
+/*
+ * This is the edge runtime version of the response object.
+ * It is a wrapper around NextResponse that adds a `status` method
+ */
+export const getResponse = (req: NextloveRequest, {
+ addIf,
+ addOkStatus
+}: {
+ addIf?: (req: NextloveRequest) => boolean
+ addOkStatus?: boolean
+}) => {
+ const json = (body, params?: ResponseInit) => {
+ const statusCode = params?.status ?? DEFAULT_STATUS;
+ const ok = statusCode >= 200 && statusCode < 300;
+
+ const shouldIncludeStatus = addIf && addOkStatus ? addIf(req) : addOkStatus;
+
+ const bodyWithPossibleOk = shouldIncludeStatus ? { ...body, ok } : body;
+
+ return NextResponse.json(bodyWithPossibleOk, params)
+ }
+
+
+ const status = (s: number) => {
+ return {
+ statusCode: s,
+ json: (body, params?: ResponseInit) => json(body, { status: s, ...params }),
+ status,
+ }
+ }
+
+ return {
+ status,
+ json,
+ statusCode: 200,
+ }
+}
\ No newline at end of file
diff --git a/packages/nextlove/src/exceptions-middleware-egde/index.ts b/packages/nextlove/src/exceptions-middleware-egde/index.ts
new file mode 100644
index 000000000..b3c508e62
--- /dev/null
+++ b/packages/nextlove/src/exceptions-middleware-egde/index.ts
@@ -0,0 +1,20 @@
+import { NextloveRequest } from "../edge-helpers";
+import unwrappedWithExceptionHandlingEdge, {
+ WithExceptionHandlingEdgeOptions,
+} from "./with-exception-handling"
+
+export interface ExceptionHandlingEdgeOptions {
+ exceptionHandlingOptions?: WithExceptionHandlingEdgeOptions
+}
+
+export const withExceptionHandlingEdge =
+ ({
+ exceptionHandlingOptions,
+ }: ExceptionHandlingEdgeOptions = {}) =>
+ (next: (req: NextloveRequest) => Promise) =>
+ (req: NextloveRequest) => {
+
+ return unwrappedWithExceptionHandlingEdge(exceptionHandlingOptions)(next)(req)
+ }
+
+export * from "../http-exceptions"
diff --git a/packages/nextlove/src/exceptions-middleware-egde/with-exception-handling.ts b/packages/nextlove/src/exceptions-middleware-egde/with-exception-handling.ts
new file mode 100644
index 000000000..f8b4be872
--- /dev/null
+++ b/packages/nextlove/src/exceptions-middleware-egde/with-exception-handling.ts
@@ -0,0 +1,59 @@
+import { NextloveRequest } from "../edge-helpers"
+import { HttpException } from "../http-exceptions"
+
+export interface WithExceptionHandlingEdgeOptions {
+ getErrorContext?: (
+ req: NextloveRequest,
+ error: Error
+ ) => Record
+}
+
+const withExceptionHandlingEdge =
+ (options: WithExceptionHandlingEdgeOptions = {}) =>
+ (next: (req: NextloveRequest) => Promise) =>
+ async (req: NextloveRequest) => {
+ const res = req.responseEdge
+ try {
+ return await next(req)
+ } catch (error: unknown) {
+ let errorContext: any = {}
+
+ if (error instanceof Error) {
+ errorContext.stack = error.stack
+ }
+
+ errorContext = options.getErrorContext
+ ? options.getErrorContext(req, errorContext)
+ : errorContext
+
+ if (error instanceof HttpException) {
+ if (error.options.json) {
+ return res.status(error.status).json({
+ error: {
+ ...error.metadata,
+ ...errorContext,
+ },
+ })
+ } else {
+ // REVIEW: we don't have the .end() method in Edge
+ return res
+ .status(error.status)
+ .json({ error: { message: error.metadata.message } })
+ }
+ } else {
+ const formattedError = new HttpException(500, {
+ type: "internal_server_error",
+ message: error instanceof Error ? error.message : "Unknown error",
+ })
+
+ return res.status(500).json({
+ error: {
+ ...formattedError.metadata,
+ ...errorContext,
+ },
+ })
+ }
+ }
+ }
+
+export default withExceptionHandlingEdge
diff --git a/packages/nextlove/src/exceptions-middleware-nodejs/index.ts b/packages/nextlove/src/exceptions-middleware-nodejs/index.ts
new file mode 100644
index 000000000..07cd25585
--- /dev/null
+++ b/packages/nextlove/src/exceptions-middleware-nodejs/index.ts
@@ -0,0 +1,32 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import unwrappedWithExceptionHandling, {
+ WithExceptionHandlingOptions,
+} from "./with-exception-handling";
+import withOkStatus, { WithOkStatusOptions } from "./with-ok-status";
+
+export interface ExceptionHandlingOptions {
+ addOkStatus?: boolean;
+ okStatusOptions?: WithOkStatusOptions;
+ exceptionHandlingOptions?: WithExceptionHandlingOptions;
+}
+
+export const withExceptionHandling =
+ ({
+ addOkStatus = false,
+ okStatusOptions,
+ exceptionHandlingOptions,
+ }: ExceptionHandlingOptions = {}) =>
+ (next: (req: NextApiRequest, res: NextApiResponse) => Promise) =>
+ (req: NextApiRequest, res: NextApiResponse) => {
+ if (addOkStatus) {
+ return withOkStatus(okStatusOptions)(
+ unwrappedWithExceptionHandling(exceptionHandlingOptions)(next)
+ )(req, res);
+ }
+
+ return unwrappedWithExceptionHandling(exceptionHandlingOptions)(next)(
+ req,
+ res
+ );
+ };
+
diff --git a/packages/nextlove/src/exceptions-middleware-nodejs/with-exception-handling.ts b/packages/nextlove/src/exceptions-middleware-nodejs/with-exception-handling.ts
new file mode 100644
index 000000000..931d38a26
--- /dev/null
+++ b/packages/nextlove/src/exceptions-middleware-nodejs/with-exception-handling.ts
@@ -0,0 +1,58 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import { HttpException } from "../http-exceptions";
+
+export interface WithExceptionHandlingOptions {
+ getErrorContext?: (
+ req: NextApiRequest,
+ error: Error
+ ) => Record;
+}
+
+const withExceptionHandling =
+ (options: WithExceptionHandlingOptions = {}) =>
+ (next: (req: NextApiRequest, res: NextApiResponse) => Promise) =>
+ async (req: NextApiRequest, res: NextApiResponse) => {
+ try {
+ await next(req, res);
+ } catch (error: unknown) {
+ let errorContext: any = {};
+
+ if (error instanceof Error) {
+ errorContext.stack = error.stack;
+ }
+
+ errorContext = options.getErrorContext
+ ? options.getErrorContext(req, errorContext)
+ : errorContext;
+
+ if (error instanceof HttpException) {
+ if (error.options.json) {
+ res.status(error.status).json({
+ error: {
+ ...error.metadata,
+ ...errorContext,
+ },
+ });
+ return;
+ } else {
+ res.status(error.status).end(error.metadata.message);
+ return;
+ }
+ } else {
+ const formattedError = new HttpException(500, {
+ type: "internal_server_error",
+ message: error instanceof Error ? error.message : "Unknown error",
+ });
+
+ res.status(500).json({
+ error: {
+ ...formattedError.metadata,
+ ...errorContext,
+ },
+ });
+ return;
+ }
+ }
+ };
+
+export default withExceptionHandling;
diff --git a/packages/nextlove/src/exceptions-middleware-nodejs/with-ok-status.ts b/packages/nextlove/src/exceptions-middleware-nodejs/with-ok-status.ts
new file mode 100644
index 000000000..26d450ea2
--- /dev/null
+++ b/packages/nextlove/src/exceptions-middleware-nodejs/with-ok-status.ts
@@ -0,0 +1,31 @@
+import { NextApiRequest, NextApiResponse } from "next";
+
+export interface WithOkStatusOptions {
+ addIf?: (req: NextApiRequest) => boolean;
+}
+
+const withOkStatus =
+ (options: WithOkStatusOptions = {}) =>
+ (next: (req: NextApiRequest, res: NextApiResponse) => Promise) =>
+ async (req: NextApiRequest, res: NextApiResponse) => {
+ // Patch .json()
+ const originalJson = res.json;
+
+ res.json = function (data) {
+ const ok = res.statusCode >= 200 && res.statusCode < 300;
+ const shouldIncludeStatus = options.addIf ? options.addIf(req) : true;
+
+ if (shouldIncludeStatus) {
+ originalJson.call(this, {
+ ...data,
+ ok,
+ });
+ } else {
+ originalJson.call(this, data);
+ }
+ };
+
+ await next(req, res);
+ };
+
+export default withOkStatus;
diff --git a/packages/nextlove/src/http-exceptions.ts b/packages/nextlove/src/http-exceptions.ts
new file mode 100644
index 000000000..b14f1e14d
--- /dev/null
+++ b/packages/nextlove/src/http-exceptions.ts
@@ -0,0 +1,105 @@
+export type HttpExceptionMetadata = {
+ type: string;
+ message: string;
+ data?: Record;
+} & Record;
+
+export interface ThrowingOptions {
+ json?: boolean;
+}
+
+/**
+ * Throw HttpExceptions inside API endpoints to generate nice error messages
+ *
+ * @example
+ * ```
+ * if (bad_soups.includes(soup_param)) {
+ * throw new HttpException(400, {
+ * type: "cant_make_soup",
+ * message: "Soup was too difficult, please specify a different soup"
+ * })
+ * }
+ * ```
+ *
+ **/
+export class HttpException extends Error {
+ options: ThrowingOptions;
+
+ constructor(
+ public status: number,
+ public metadata: HttpExceptionMetadata,
+ options?: ThrowingOptions
+ ) {
+ super(metadata.message);
+
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, this.constructor);
+ }
+
+ this.options = options ?? { json: true };
+ }
+
+ toString() {
+ return `HttpException: ${this.status}, ${this.metadata.message} (${this.metadata.type})`;
+ }
+}
+
+/**
+ * Throw BadRequestException inside API endpoints that were provided incorrect
+ * parameters or body
+ *
+ * @example
+ * ```
+ * if (bad_soups.includes(soup_param)) {
+ * throw new BadRequestException({
+ * type: "cant_make_soup",
+ * message: "Soup was too difficult, please specify a different soup",
+ * })
+ * }
+ * ```
+ *
+ **/
+export class BadRequestException extends HttpException {
+ constructor(
+ public metadata: HttpExceptionMetadata,
+ options?: ThrowingOptions
+ ) {
+ super(400, metadata, options);
+ }
+}
+
+export class UnauthorizedException extends HttpException {
+ constructor(
+ public metadata: HttpExceptionMetadata,
+ options?: ThrowingOptions
+ ) {
+ super(401, metadata, options);
+ }
+}
+
+export class NotFoundException extends HttpException {
+ constructor(
+ public metadata: HttpExceptionMetadata,
+ options?: ThrowingOptions
+ ) {
+ super(404, metadata, options);
+ }
+}
+
+export class MethodNotAllowedException extends HttpException {
+ constructor(
+ public metadata: HttpExceptionMetadata,
+ options?: ThrowingOptions
+ ) {
+ super(405, metadata, options);
+ }
+}
+
+export class InternalServerErrorException extends HttpException {
+ constructor(
+ public metadata: HttpExceptionMetadata,
+ options?: ThrowingOptions
+ ) {
+ super(500, metadata, options);
+ }
+}
diff --git a/packages/nextlove/src/index.ts b/packages/nextlove/src/index.ts
index 73ee6480a..17fffb11c 100644
--- a/packages/nextlove/src/index.ts
+++ b/packages/nextlove/src/index.ts
@@ -1,5 +1,10 @@
-export * from "nextjs-exception-middleware"
-export * from "./with-route-spec"
-export { wrappers } from "nextjs-middleware-wrappers"
+export * from "./exceptions-middleware-egde"
+export * from "./exceptions-middleware-nodejs"
export * from "./types"
-export * from "./generate-openapi"
+export * from "./with-route-spec"
+export * from "./with-route-spec-edge"
+export * from "./http-exceptions"
+export * from "./types-edge"
+export * from "./wrappers-edge"
+export * from "./wrappers-nodejs"
+
diff --git a/packages/nextlove/src/types-edge/index.ts b/packages/nextlove/src/types-edge/index.ts
new file mode 100644
index 000000000..61da2f532
--- /dev/null
+++ b/packages/nextlove/src/types-edge/index.ts
@@ -0,0 +1,152 @@
+import { z } from "zod"
+import { SecuritySchemeObject } from "openapi3-ts"
+import { NextloveRequest, NextloveResponse } from "../edge-helpers"
+import { NextResponse } from "next/server"
+import { MiddlewareEdge } from "../wrappers-edge"
+
+type ParamDef = z.ZodTypeAny | z.ZodEffects
+
+export interface RouteSpecEdge<
+ Auth extends string = string,
+ // we don't need the withMethods middleware in Edge
+ // Methods extends HTTPMethods[] = any,
+ JsonBody extends ParamDef = z.ZodObject,
+ QueryParams extends ParamDef = z.ZodObject,
+ CommonParams extends ParamDef = z.ZodObject,
+ Middlewares extends readonly MiddlewareEdge[] = any[],
+ JsonResponse extends ParamDef = z.ZodObject,
+ FormData extends ParamDef = z.ZodTypeAny
+> {
+ // we don't need the withMethods middleware in Edge
+ // methods: Methods
+ auth: Auth
+ jsonBody?: JsonBody
+ queryParams?: QueryParams
+ commonParams?: CommonParams
+ middlewares?: Middlewares
+ jsonResponse?: JsonResponse
+ formData?: FormData
+}
+
+export type MiddlewareEdgeChainOutput<
+ MWChain extends readonly MiddlewareEdge[]
+> = MWChain extends readonly []
+ ? {}
+ : MWChain extends readonly [infer First, ...infer Rest]
+ ? First extends MiddlewareEdge
+ ? T &
+ (Rest extends readonly MiddlewareEdge[]
+ ? MiddlewareEdgeChainOutput
+ : never)
+ : never
+ : never
+
+export type AuthMiddlewaresEdge = {
+ [auth_type: string]: MiddlewareEdge
+}
+
+export interface SetupParamsEdge<
+ AuthMW extends AuthMiddlewaresEdge = AuthMiddlewaresEdge,
+ GlobalMW extends MiddlewareEdge[] = any[]
+> {
+ authMiddlewareMap: AuthMW
+ globalMiddlewares: GlobalMW
+ exceptionHandlingMiddleware?: ((next: Function) => Function) | null
+
+ // These improve OpenAPI generation
+ apiName: string
+ productionServerUrl: string
+
+ addOkStatus?: boolean
+ okStatusOptions?: {
+ addIf?: (req: NextloveRequest) => boolean;
+ }
+
+ shouldValidateResponses?: boolean
+ shouldValidateGetRequestBody?: boolean
+ securitySchemas?: Record
+}
+
+const defaultMiddlewareMap = {
+ none: (next) => next,
+} as const
+
+type Send = (body: T, params?: ResponseInit) => NextResponse
+type NextloveResponseWithoutJsonAndStatusMethods = Omit<
+ NextloveResponse,
+ "json" | "status"
+>
+
+type SuccessfulNextloveResponseMethods = {
+ status: (
+ statusCode: 200 | 201
+ ) => NextloveResponseWithoutJsonAndStatusMethods & {
+ json: Send
+ }
+ json: Send
+}
+
+type ErrorNextloveResponseMethods = {
+ status: (statusCode: number) => NextloveResponseWithoutJsonAndStatusMethods & {
+ json: Send
+ }
+ json: Send
+}
+
+export type RouteEdgeFunction<
+ SP extends SetupParamsEdge,
+ RS extends RouteSpecEdge
+> = (
+ req: (SP["authMiddlewareMap"] &
+ typeof defaultMiddlewareMap)[RS["auth"]] extends MiddlewareEdge<
+ infer AuthMWOut,
+ any
+ >
+ ? Omit &
+ AuthMWOut &
+ MiddlewareEdgeChainOutput<
+ RS["middlewares"] extends readonly MiddlewareEdge[]
+ ? [...SP["globalMiddlewares"], ...RS["middlewares"]]
+ : SP["globalMiddlewares"]
+ > & {
+ edgeBody: RS["formData"] extends z.ZodTypeAny
+ ? z.infer
+ : RS["jsonBody"] extends z.ZodTypeAny
+ ? z.infer
+ : {}
+ edgeQuery: RS["queryParams"] extends z.ZodTypeAny
+ ? z.infer
+ : {}
+ commonParams: RS["commonParams"] extends z.ZodTypeAny
+ ? z.infer
+ : {}
+ responseEdge: NextloveResponseWithoutJsonAndStatusMethods &
+ SuccessfulNextloveResponseMethods<
+ RS["jsonResponse"] extends z.ZodTypeAny
+ ? z.infer
+ : any
+ > &
+ ErrorNextloveResponseMethods
+ }
+ : `unknown auth type: ${RS["auth"]}. You should configure this auth type in your auth_middlewares w/ createWithRouteSpec, or maybe you need to add "as const" to your route spec definition.`,
+) => NextResponse | Promise
+
+export type CreateWithRouteSpecEdgeFunction = <
+ SP extends SetupParamsEdge
+>(
+ setupParams: SP
+) => <
+ RS extends RouteSpecEdge<
+ string,
+ // we don't need the withMethods middleware in Edge
+ // any,
+ any,
+ any,
+ any,
+ any,
+ z.ZodObject,
+ any
+ >
+>(
+ route_spec: RS
+) => (next: RouteEdgeFunction) => any
diff --git a/packages/nextlove/src/types/index.ts b/packages/nextlove/src/types/index.ts
index 9781ce88b..320c3a6bf 100644
--- a/packages/nextlove/src/types/index.ts
+++ b/packages/nextlove/src/types/index.ts
@@ -1,16 +1,8 @@
import { NextApiResponse, NextApiRequest } from "next"
-import { Middleware as WrapperMiddleware } from "nextjs-middleware-wrappers"
import { z } from "zod"
import { HTTPMethods } from "../with-route-spec/middlewares/with-methods"
-import { SecuritySchemeObject, SecurityRequirementObject } from "openapi3-ts"
-
-export type Middleware = WrapperMiddleware & {
- /**
- * @deprecated moved to setupParams
- */
- securitySchema?: SecuritySchemeObject
- securityObjects?: SecurityRequirementObject[]
-}
+import { SecuritySchemeObject } from "openapi3-ts"
+import { Middleware } from "../wrappers-nodejs"
type ParamDef = z.ZodTypeAny | z.ZodEffects
diff --git a/packages/nextlove/src/with-route-spec-edge/index.ts b/packages/nextlove/src/with-route-spec-edge/index.ts
new file mode 100644
index 000000000..21a7460bb
--- /dev/null
+++ b/packages/nextlove/src/with-route-spec-edge/index.ts
@@ -0,0 +1,82 @@
+import { wrappersEdge } from "../wrappers-edge"
+import { withValidationEdge } from "./with-validation-edge"
+import { NextloveRequest, getResponse } from "../edge-helpers"
+import { CreateWithRouteSpecEdgeFunction, RouteSpecEdge } from "../types-edge"
+import { withExceptionHandlingEdge } from "../exceptions-middleware-egde"
+
+export const createWithRouteSpecEdge: CreateWithRouteSpecEdgeFunction = ((
+ setupParams
+) => {
+ const {
+ authMiddlewareMap = {},
+ globalMiddlewares = [],
+ shouldValidateResponses,
+ shouldValidateGetRequestBody = true,
+ exceptionHandlingMiddleware = withExceptionHandlingEdge({
+ exceptionHandlingOptions: {
+ getErrorContext: (req, error) => {
+ if (process.env.NODE_ENV === "production") {
+ return {}
+ }
+
+ return error
+ },
+ },
+ }) as any,
+ } = setupParams
+
+ const withRouteSpec = (spec: RouteSpecEdge) => {
+ const createRouteExport = (userDefinedRouteFn: (req: NextloveRequest) => any) => {
+ const rootRequestHandler = async (
+ req: NextloveRequest,
+ ) => {
+ req.responseEdge = getResponse(req, {
+ addIf: setupParams.okStatusOptions?.addIf,
+ addOkStatus: setupParams.addOkStatus,
+ })
+
+ authMiddlewareMap["none"] = (next) => next;
+
+ const auth_middleware = authMiddlewareMap[spec.auth]
+ if (!auth_middleware) throw new Error(`Unknown auth type: ${spec.auth}`)
+
+ return wrappersEdge(
+ ...((exceptionHandlingMiddleware
+ ? [exceptionHandlingMiddleware]
+ : []) as [any]),
+ ...((globalMiddlewares || []) as []),
+ auth_middleware,
+ ...((spec.middlewares || []) as []),
+ // we don't need the withMethods middleware in Edge
+ // withMethods(spec.methods),
+ withValidationEdge({
+ jsonBody: spec.jsonBody,
+ queryParams: spec.queryParams,
+ commonParams: spec.commonParams,
+ formData: spec.formData,
+ jsonResponse: spec.jsonResponse,
+ shouldValidateResponses,
+ shouldValidateGetRequestBody,
+ }),
+ userDefinedRouteFn
+ )(req)
+ }
+
+ rootRequestHandler._setupParams = setupParams
+ rootRequestHandler._routeSpec = spec
+
+ return rootRequestHandler
+ }
+
+ createRouteExport._setupParams = setupParams
+ createRouteExport._routeSpec = spec
+
+ return createRouteExport
+ }
+
+ withRouteSpec._setupParams = setupParams
+
+ return withRouteSpec
+}) as any
diff --git a/packages/nextlove/src/with-route-spec-edge/with-validation-edge.ts b/packages/nextlove/src/with-route-spec-edge/with-validation-edge.ts
new file mode 100644
index 000000000..fc68549b5
--- /dev/null
+++ b/packages/nextlove/src/with-route-spec-edge/with-validation-edge.ts
@@ -0,0 +1,191 @@
+import { z } from "zod"
+import {
+ BadRequestException,
+ InternalServerErrorException,
+} from "../http-exceptions"
+import { isEmpty } from "lodash"
+import { NextloveRequest } from "../edge-helpers"
+import { parseQueryParams, zodIssueToString } from "../zod-helpers"
+
+export interface RequestInput<
+ JsonBody extends z.ZodTypeAny,
+ QueryParams extends z.ZodTypeAny,
+ CommonParams extends z.ZodTypeAny,
+ FormData extends z.ZodTypeAny,
+ JsonResponse extends z.ZodTypeAny
+> {
+ jsonBody?: JsonBody
+ queryParams?: QueryParams
+ commonParams?: CommonParams
+ formData?: FormData
+ jsonResponse?: JsonResponse
+ shouldValidateResponses?: boolean
+ shouldValidateGetRequestBody?: boolean
+}
+
+// NOTE: we should be able to use the same validation logic for both the nodejs and edge runtime
+function validateJsonResponse(
+ jsonResponse: JsonResponse | undefined,
+ req: NextloveRequest
+) {
+ const original_res_json = req.responseEdge.json
+ const override_res_json: NextloveRequest["responseEdge"]["json"] = (
+ body,
+ params
+ ) => {
+ const is_success =
+ req.responseEdge.statusCode >= 200 && req.responseEdge.statusCode < 300
+ if (!is_success) {
+ return original_res_json(body, params)
+ }
+
+ try {
+ jsonResponse?.parse(body)
+ } catch (err) {
+ throw new InternalServerErrorException({
+ type: "invalid_response",
+ message: "the response does not match with jsonResponse",
+ zodError: err,
+ })
+ }
+
+ return original_res_json(body, params)
+ }
+
+ req.responseEdge.json = override_res_json
+}
+
+export const withValidationEdge =
+ <
+ JsonBody extends z.ZodTypeAny,
+ QueryParams extends z.ZodTypeAny,
+ CommonParams extends z.ZodTypeAny,
+ FormData extends z.ZodTypeAny,
+ JsonResponse extends z.ZodTypeAny
+ >(
+ input: RequestInput<
+ JsonBody,
+ QueryParams,
+ CommonParams,
+ FormData,
+ JsonResponse
+ >
+ ) =>
+ (next) =>
+ async (req: NextloveRequest) => {
+ if (
+ (input.formData && input.jsonBody) ||
+ (input.formData && input.commonParams)
+ ) {
+ throw new Error("Cannot use formData with jsonBody or commonParams")
+ }
+ const { searchParams } = new URL(req.url)
+ const paramsArray = Array.from(searchParams.entries())
+ let edgeQuery = Object.fromEntries(paramsArray)
+
+ const isBodyPresent = !!req.body
+
+ let bodyEdge: any
+ if (isBodyPresent) {
+ bodyEdge = await req.json()
+ }
+
+ const contentType = req.headers.get("content-type")
+
+ const isContentTypeJson = contentType?.includes("application/json")
+ const isContentTypeFormUrlEncoded = contentType?.includes(
+ "application/x-www-form-urlencoded"
+ )
+
+ if (
+ (req.method === "POST" || req.method === "PATCH") &&
+ (input.jsonBody || input.commonParams) &&
+ !isContentTypeJson &&
+ !isEmpty(bodyEdge)
+ ) {
+ throw new BadRequestException({
+ type: "invalid_content_type",
+ message: `${req.method} requests must have Content-Type header with "application/json"`,
+ })
+ }
+
+ if (
+ input.formData &&
+ req.method !== "GET" &&
+ !isContentTypeFormUrlEncoded
+ // TODO eventually we should support multipart/form-data
+ ) {
+ throw new BadRequestException({
+ type: "invalid_content_type",
+ message: `Must have Content-Type header with "application/x-www-form-urlencoded"`,
+ })
+ }
+
+ try {
+ const original_combined_params = { ...edgeQuery, ...bodyEdge }
+
+ const willValidateRequestBody = input.shouldValidateGetRequestBody
+ ? true
+ : req.method !== "GET"
+
+ const isFormData = Boolean(input.formData)
+
+ if (isFormData && willValidateRequestBody) {
+ ;(req as any).edgeBody = input.formData?.parse(bodyEdge)
+ }
+
+ if (!isFormData && willValidateRequestBody) {
+ ;(req as any).edgeBody = input.jsonBody?.parse(bodyEdge)
+ }
+
+ if (input.queryParams) {
+ ;(req as any).edgeQuery = parseQueryParams(input.queryParams, edgeQuery)
+ }
+
+ if (input.commonParams) {
+ /**
+ * as commonParams includes query params, we can use the parseQueryParams function
+ */
+ ;(req as any).commonParams = parseQueryParams(
+ input.commonParams,
+ original_combined_params
+ )
+ }
+ } catch (error: any) {
+ if (error.name === "ZodError") {
+ let message
+ if (error.issues.length === 1) {
+ const issue = error.issues[0]
+ message = zodIssueToString(issue)
+ } else {
+ const message_components: string[] = []
+ for (const issue of error.issues) {
+ message_components.push(zodIssueToString(issue))
+ }
+ message =
+ `${error.issues.length} Input Errors: ` +
+ message_components.join(", ")
+ }
+
+ throw new BadRequestException({
+ type: "invalid_input",
+ message,
+ validation_errors: error.format(),
+ })
+ }
+
+ throw new BadRequestException({
+ type: "invalid_input",
+ message: "Error while parsing input",
+ })
+ }
+
+ /**
+ * this will override the res.json method to validate the response
+ */
+ if (input.shouldValidateResponses) {
+ validateJsonResponse(input.jsonResponse, req)
+ }
+
+ return next(req)
+ }
diff --git a/packages/nextlove/src/with-route-spec/index.ts b/packages/nextlove/src/with-route-spec/index.ts
index b9533bda5..b5cd3bc4c 100644
--- a/packages/nextlove/src/with-route-spec/index.ts
+++ b/packages/nextlove/src/with-route-spec/index.ts
@@ -1,6 +1,6 @@
import { NextApiResponse, NextApiRequest } from "next"
-import { withExceptionHandling } from "nextjs-exception-middleware"
-import wrappers, { Middleware } from "nextjs-middleware-wrappers"
+import { withExceptionHandling } from "../exceptions-middleware-nodejs"
+import wrappers, { Middleware } from "../wrappers-nodejs"
import { CreateWithRouteSpecFunction, RouteSpec } from "../types"
import withMethods, { HTTPMethods } from "./middlewares/with-methods"
import withValidation from "./middlewares/with-validation"
@@ -49,7 +49,6 @@ export const createWithRouteSpec: CreateWithRouteSpecFunction = ((
authMiddlewareMap = {},
globalMiddlewares = [],
shouldValidateResponses,
- shouldValidateGetRequestBody = true,
exceptionHandlingMiddleware = withExceptionHandling({
addOkStatus: setupParams.addOkStatus,
exceptionHandlingOptions: {
@@ -90,7 +89,6 @@ export const createWithRouteSpec: CreateWithRouteSpecFunction = ((
formData: spec.formData,
jsonResponse: spec.jsonResponse,
shouldValidateResponses,
- shouldValidateGetRequestBody,
}),
userDefinedRouteFn
)(req as any, res)
diff --git a/packages/nextlove/src/with-route-spec/middlewares/with-methods.ts b/packages/nextlove/src/with-route-spec/middlewares/with-methods.ts
index 87b26d556..86bf6c64f 100644
--- a/packages/nextlove/src/with-route-spec/middlewares/with-methods.ts
+++ b/packages/nextlove/src/with-route-spec/middlewares/with-methods.ts
@@ -1,4 +1,4 @@
-import { MethodNotAllowedException } from "nextjs-exception-middleware"
+import { MethodNotAllowedException } from "../../http-exceptions"
export type HTTPMethods =
| "GET"
diff --git a/packages/nextlove/src/with-route-spec/middlewares/with-validation.ts b/packages/nextlove/src/with-route-spec/middlewares/with-validation.ts
index 0ba56d291..2b15acf66 100644
--- a/packages/nextlove/src/with-route-spec/middlewares/with-validation.ts
+++ b/packages/nextlove/src/with-route-spec/middlewares/with-validation.ts
@@ -1,99 +1,12 @@
import type { NextApiRequest, NextApiResponse } from "next"
-import { z, ZodFirstPartyTypeKind } from "zod"
+import { z } from "zod"
import {
BadRequestException,
InternalServerErrorException,
-} from "nextjs-exception-middleware"
+} from "../../http-exceptions"
import { isEmpty } from "lodash"
+import { parseQueryParams, zodIssueToString } from "../../zod-helpers"
-const getZodObjectSchemaFromZodEffectSchema = (
- isZodEffect: boolean,
- schema: z.ZodTypeAny
-): z.ZodTypeAny | z.ZodObject => {
- if (!isZodEffect) {
- return schema as z.ZodObject
- }
-
- let currentSchema = schema
-
- while (currentSchema instanceof z.ZodEffects) {
- currentSchema = currentSchema._def.schema
- }
-
- return currentSchema as z.ZodObject
-}
-
-/**
- * This function is used to get the correct schema from a ZodEffect | ZodDefault | ZodOptional schema.
- * TODO: this function should handle all special cases of ZodSchema and not just ZodEffect | ZodDefault | ZodOptional
- */
-const getZodDefFromZodSchemaHelpers = (schema: z.ZodTypeAny) => {
- const special_zod_types = [
- ZodFirstPartyTypeKind.ZodOptional,
- ZodFirstPartyTypeKind.ZodDefault,
- ZodFirstPartyTypeKind.ZodEffects,
- ]
-
- while (special_zod_types.includes(schema._def.typeName)) {
- if (
- schema._def.typeName === ZodFirstPartyTypeKind.ZodOptional ||
- schema._def.typeName === ZodFirstPartyTypeKind.ZodDefault
- ) {
- schema = schema._def.innerType
- continue
- }
-
- if (schema._def.typeName === ZodFirstPartyTypeKind.ZodEffects) {
- schema = schema._def.schema
- continue
- }
- }
- return schema._def
-}
-
-const parseQueryParams = (
- schema: z.ZodTypeAny,
- input: Record
-) => {
- const parsed_input = Object.assign({}, input)
- const isZodEffect = schema._def.typeName === ZodFirstPartyTypeKind.ZodEffects
- const safe_schema = getZodObjectSchemaFromZodEffectSchema(isZodEffect, schema)
- const isZodObject =
- safe_schema._def.typeName === ZodFirstPartyTypeKind.ZodObject
-
- if (isZodObject) {
- const obj_schema = safe_schema as z.ZodObject
-
- for (const [key, value] of Object.entries(obj_schema.shape)) {
- const def = getZodDefFromZodSchemaHelpers(value as z.ZodTypeAny)
- const isArray = def.typeName === ZodFirstPartyTypeKind.ZodArray
- if (isArray) {
- const array_input = input[key]
-
- if (typeof array_input === "string") {
- parsed_input[key] = array_input.split(",")
- }
-
- if (Array.isArray(input[`${key}[]`])) {
- parsed_input[key] = input[`${key}[]`]
- }
-
- continue
- }
-
- const isBoolean = def.typeName === ZodFirstPartyTypeKind.ZodBoolean
- if (isBoolean) {
- const boolean_input = input[key]
-
- if (typeof boolean_input === "string") {
- parsed_input[key] = boolean_input === "true"
- }
- }
- }
- }
-
- return schema.parse(parsed_input)
-}
export interface RequestInput<
JsonBody extends z.ZodTypeAny,
@@ -111,16 +24,6 @@ export interface RequestInput<
shouldValidateGetRequestBody?: boolean
}
-const zodIssueToString = (issue: z.ZodIssue) => {
- if (issue.path.join(".") === "") {
- return issue.message
- }
- if (issue.message === "Required") {
- return `${issue.path.join(".")} is required`
- }
- return `${issue.message} for "${issue.path.join(".")}"`
-}
-
function validateJsonResponse(
jsonResponse: JsonResponse | undefined,
res: NextApiResponse
diff --git a/packages/nextlove/src/wrappers-edge.ts b/packages/nextlove/src/wrappers-edge.ts
new file mode 100644
index 000000000..50e57d9b9
--- /dev/null
+++ b/packages/nextlove/src/wrappers-edge.ts
@@ -0,0 +1,320 @@
+import type { NextRequest as Req } from "next/server"
+/*
+
+Wraps a function in layers of other functions, while preserving the input/output
+type. The output of wrappers will always have the type of it's last parameter
+(the wrapped function)
+
+This function turns this type of composition...
+
+logger.withContext("somecontext")(
+ async (a, b) => {
+ return a
+ }
+)
+
+Into...
+
+wrappers(
+ logger.withContext("somecontext"),
+ async (a, b) => {
+ return a
+ }
+)
+
+Having this as a utility method helps preserve types, which otherwise can get
+messed up by the middlewares. It also can make the code cleaner where there are
+multiple wrappers.
+
+## EXAMPLES
+
+In the context of request middleware you might write something like this...
+
+const withRequestLoggingMiddleware = (next) => async (req, res) => {
+ console.log(`GOT REQUEST ${req.method} ${req.path}`)
+ return next(req, res)
+}
+
+Here's an example of a wrapper that takes some parameters...
+
+const withLoggedArguments =
+ (logPrefix: string) =>
+ (next) =>
+ async (...funcArgs) => {
+ console.log(logPrefix, ...funcArgs)
+ return next(...funcArgs)
+ }
+
+*/
+
+export type MiddlewareEdge = (
+ next: (req: Req & Dep & T) => any
+) => (req: Req & Dep & T) => any
+
+// Safer Middleware requires the use of extendRequest to ensure that the
+// new context (T) was actually added to the request. It's kind of annoying
+// to use in practice, so we don't use it for our Wrappers (yet)
+export type SaferMiddlewareEgde = (
+ next: (req: Req & Dep & T) => any
+) => (req: Req & Dep) => any
+
+export const extendRequestEdge = >(req: T, merge: K): T & K => {
+ for (const [key, v] of Object.entries(merge)) {
+ ;(req as any)[key] = v
+ }
+ return req as any
+}
+
+type WrappersEdge1 = (
+ mw1: MiddlewareEdge,
+ endpoint: (req: Req & Mw1RequestContext) => any
+) => (req: Req) => any
+
+type WrappersEdge2 = <
+ Mw1RequestContext extends Mw2Dep,
+ Mw1Dep,
+ Mw2RequestContext,
+ Mw2Dep
+>(
+ mw1: MiddlewareEdge,
+ mw2: MiddlewareEdge,
+ endpoint: (req: Req & Mw1RequestContext & Mw2RequestContext) => any
+) => (req: Req) => any
+
+// TODO figure out how to do a recursive definition, or one that simplifies
+// these redundant WrappersEdge
+
+type WrappersEdge3 = <
+ Mw1RequestContext extends Mw2Dep,
+ Mw1Dep,
+ Mw2RequestContext,
+ Mw2Dep,
+ Mw3RequestContext,
+ Mw3Dep
+>(
+ mw1: MiddlewareEdge,
+ mw2: MiddlewareEdge,
+ mw3: MiddlewareEdge<
+ Mw3RequestContext,
+ Mw1RequestContext & Mw2RequestContext extends Mw3Dep ? Mw3Dep : never
+ >,
+ endpoint: (
+ req: Req & Mw1RequestContext & Mw2RequestContext & Mw3RequestContext
+ ) => any
+) => (req: Req) => any
+
+type WrappersEdge4 = <
+ Mw1RequestContext extends Mw2Dep,
+ Mw1Dep,
+ Mw2RequestContext,
+ Mw2Dep,
+ Mw3RequestContext,
+ Mw3Dep,
+ Mw4RequestContext,
+ Mw4Dep
+>(
+ mw1: MiddlewareEdge,
+ mw2: MiddlewareEdge,
+ mw3: MiddlewareEdge<
+ Mw3RequestContext,
+ Mw1RequestContext & Mw2RequestContext extends Mw3Dep ? Mw3Dep : never
+ >,
+ mw4: MiddlewareEdge<
+ Mw4RequestContext,
+ Mw1RequestContext & Mw2RequestContext & Mw3RequestContext extends Mw4Dep
+ ? Mw4Dep
+ : never
+ >,
+ endpoint: (
+ req: Req & Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext
+ ) => any
+) => (req: Req) => any
+
+type WrappersEdge5 = <
+ Mw1RequestContext extends Mw2Dep,
+ Mw1Dep,
+ Mw2RequestContext,
+ Mw2Dep,
+ Mw3RequestContext,
+ Mw3Dep,
+ Mw4RequestContext,
+ Mw4Dep,
+ Mw5RequestContext,
+ Mw5Dep
+>(
+ mw1: MiddlewareEdge,
+ mw2: MiddlewareEdge,
+ mw3: MiddlewareEdge<
+ Mw3RequestContext,
+ Mw1RequestContext & Mw2RequestContext extends Mw3Dep ? Mw3Dep : never
+ >,
+ mw4: MiddlewareEdge<
+ Mw4RequestContext,
+ Mw1RequestContext & Mw2RequestContext & Mw3RequestContext extends Mw4Dep
+ ? Mw4Dep
+ : never
+ >,
+ mw5: MiddlewareEdge<
+ Mw5RequestContext,
+ Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext extends Mw5Dep
+ ? Mw5Dep
+ : never
+ >,
+ endpoint: (
+ req: Req & Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext &
+ Mw5RequestContext
+ ) => any
+) => (req: Req) => any
+
+type WrappersEdge6 = <
+ Mw1RequestContext extends Mw2Dep,
+ Mw1Dep,
+ Mw2RequestContext,
+ Mw2Dep,
+ Mw3RequestContext,
+ Mw3Dep,
+ Mw4RequestContext,
+ Mw4Dep,
+ Mw5RequestContext,
+ Mw5Dep,
+ Mw6RequestContext,
+ Mw6Dep
+>(
+ mw1: MiddlewareEdge,
+ mw2: MiddlewareEdge,
+ mw3: MiddlewareEdge<
+ Mw3RequestContext,
+ Mw1RequestContext & Mw2RequestContext extends Mw3Dep ? Mw3Dep : never
+ >,
+ mw4: MiddlewareEdge<
+ Mw4RequestContext,
+ Mw1RequestContext & Mw2RequestContext & Mw3RequestContext extends Mw4Dep
+ ? Mw4Dep
+ : never
+ >,
+ mw5: MiddlewareEdge<
+ Mw5RequestContext,
+ Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext extends Mw5Dep
+ ? Mw5Dep
+ : never
+ >,
+ mw6: MiddlewareEdge<
+ Mw6RequestContext,
+ Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext &
+ Mw5RequestContext extends Mw6Dep
+ ? Mw6Dep
+ : never
+ >,
+ endpoint: (
+ req: Req & Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext &
+ Mw5RequestContext &
+ Mw6RequestContext
+ ) => any
+) => (req: Req) => any
+
+type WrappersEdge7 = <
+ Mw1RequestContext extends Mw2Dep,
+ Mw1Dep,
+ Mw2RequestContext,
+ Mw2Dep,
+ Mw3RequestContext,
+ Mw3Dep,
+ Mw4RequestContext,
+ Mw4Dep,
+ Mw5RequestContext,
+ Mw5Dep,
+ Mw6RequestContext,
+ Mw6Dep,
+ Mw7RequestContext,
+ Mw7Dep
+>(
+ mw1: MiddlewareEdge,
+ mw2: MiddlewareEdge,
+ mw3: MiddlewareEdge<
+ Mw3RequestContext,
+ Mw1RequestContext & Mw2RequestContext extends Mw3Dep ? Mw3Dep : never
+ >,
+ mw4: MiddlewareEdge<
+ Mw4RequestContext,
+ Mw1RequestContext & Mw2RequestContext & Mw3RequestContext extends Mw4Dep
+ ? Mw4Dep
+ : never
+ >,
+ mw5: MiddlewareEdge<
+ Mw5RequestContext,
+ Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext extends Mw5Dep
+ ? Mw5Dep
+ : never
+ >,
+ mw6: MiddlewareEdge<
+ Mw6RequestContext,
+ Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext &
+ Mw5RequestContext extends Mw6Dep
+ ? Mw6Dep
+ : never
+ >,
+ mw7: MiddlewareEdge<
+ Mw7RequestContext,
+ Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext &
+ Mw5RequestContext &
+ Mw6RequestContext extends Mw7Dep
+ ? Mw7Dep
+ : never
+ >,
+ endpoint: (
+ req: Req & Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext &
+ Mw5RequestContext &
+ Mw6RequestContext &
+ Mw7RequestContext
+ ) => any
+) => (req: Req) => any
+
+type WrappersEdge = WrappersEdge1 &
+ WrappersEdge2 &
+ WrappersEdge3 &
+ WrappersEdge4 &
+ WrappersEdge5 &
+ WrappersEdge6 &
+ WrappersEdge7
+
+export const wrappersEdge: WrappersEdge = (...wrappersArgs: any[]) => {
+ const wrappedFunction = wrappersArgs[wrappersArgs.length - 1]
+ const mws = wrappersArgs.slice(0, -1)
+
+ let lastWrappedFunction = wrappedFunction
+ for (let i = mws.length - 1; i >= 0; i--) {
+ lastWrappedFunction = (mws[i] as any)(lastWrappedFunction)
+ }
+
+ return lastWrappedFunction
+}
\ No newline at end of file
diff --git a/packages/nextlove/src/wrappers-nodejs.ts b/packages/nextlove/src/wrappers-nodejs.ts
new file mode 100644
index 000000000..0f80496fd
--- /dev/null
+++ b/packages/nextlove/src/wrappers-nodejs.ts
@@ -0,0 +1,327 @@
+import type { NextApiRequest as Req, NextApiResponse as Res } from "next"
+/*
+
+Wraps a function in layers of other functions, while preserving the input/output
+type. The output of wrappers will always have the type of it's last parameter
+(the wrapped function)
+
+This function turns this type of composition...
+
+logger.withContext("somecontext")(
+ async (a, b) => {
+ return a
+ }
+)
+
+Into...
+
+wrappers(
+ logger.withContext("somecontext"),
+ async (a, b) => {
+ return a
+ }
+)
+
+Having this as a utility method helps preserve types, which otherwise can get
+messed up by the middlewares. It also can make the code cleaner where there are
+multiple wrappers.
+
+## EXAMPLES
+
+In the context of request middleware you might write something like this...
+
+const withRequestLoggingMiddleware = (next) => async (req, res) => {
+ console.log(`GOT REQUEST ${req.method} ${req.path}`)
+ return next(req, res)
+}
+
+Here's an example of a wrapper that takes some parameters...
+
+const withLoggedArguments =
+ (logPrefix: string) =>
+ (next) =>
+ async (...funcArgs) => {
+ console.log(logPrefix, ...funcArgs)
+ return next(...funcArgs)
+ }
+
+*/
+
+export type Middleware = (
+ next: (req: Req & Dep & T, res: Res) => any
+) => (req: Req & Dep & T, res: Res) => any
+
+// Safer Middleware requires the use of extendRequest to ensure that the
+// new context (T) was actually added to the request. It's kind of annoying
+// to use in practice, so we don't use it for our Wrappers (yet)
+export type SaferMiddleware = (
+ next: (req: Req & Dep & T, res: Res) => any
+) => (req: Req & Dep, res: Res) => any
+
+export const extendRequest = >(req: T, merge: K): T & K => {
+ for (const [key, v] of Object.entries(merge)) {
+ ;(req as any)[key] = v
+ }
+ return req as any
+}
+
+type Wrappers1 = (
+ mw1: Middleware,
+ endpoint: (req: Req & Mw1RequestContext, res: Res) => any
+) => (req: Req, res: Res) => any
+
+type Wrappers2 = <
+ Mw1RequestContext extends Mw2Dep,
+ Mw1Dep,
+ Mw2RequestContext,
+ Mw2Dep
+>(
+ mw1: Middleware,
+ mw2: Middleware,
+ endpoint: (req: Req & Mw1RequestContext & Mw2RequestContext, res: Res) => any
+) => (req: Req, res: Res) => any
+
+// TODO figure out how to do a recursive definition, or one that simplifies
+// these redundant wrappers
+
+type Wrappers3 = <
+ Mw1RequestContext extends Mw2Dep,
+ Mw1Dep,
+ Mw2RequestContext,
+ Mw2Dep,
+ Mw3RequestContext,
+ Mw3Dep
+>(
+ mw1: Middleware,
+ mw2: Middleware,
+ mw3: Middleware<
+ Mw3RequestContext,
+ Mw1RequestContext & Mw2RequestContext extends Mw3Dep ? Mw3Dep : never
+ >,
+ endpoint: (
+ req: Req & Mw1RequestContext & Mw2RequestContext & Mw3RequestContext,
+ res: Res
+ ) => any
+) => (req: Req, res: Res) => any
+
+type Wrappers4 = <
+ Mw1RequestContext extends Mw2Dep,
+ Mw1Dep,
+ Mw2RequestContext,
+ Mw2Dep,
+ Mw3RequestContext,
+ Mw3Dep,
+ Mw4RequestContext,
+ Mw4Dep
+>(
+ mw1: Middleware,
+ mw2: Middleware,
+ mw3: Middleware<
+ Mw3RequestContext,
+ Mw1RequestContext & Mw2RequestContext extends Mw3Dep ? Mw3Dep : never
+ >,
+ mw4: Middleware<
+ Mw4RequestContext,
+ Mw1RequestContext & Mw2RequestContext & Mw3RequestContext extends Mw4Dep
+ ? Mw4Dep
+ : never
+ >,
+ endpoint: (
+ req: Req & Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext,
+ res: Res
+ ) => any
+) => (req: Req, res: Res) => any
+
+type Wrappers5 = <
+ Mw1RequestContext extends Mw2Dep,
+ Mw1Dep,
+ Mw2RequestContext,
+ Mw2Dep,
+ Mw3RequestContext,
+ Mw3Dep,
+ Mw4RequestContext,
+ Mw4Dep,
+ Mw5RequestContext,
+ Mw5Dep
+>(
+ mw1: Middleware,
+ mw2: Middleware,
+ mw3: Middleware<
+ Mw3RequestContext,
+ Mw1RequestContext & Mw2RequestContext extends Mw3Dep ? Mw3Dep : never
+ >,
+ mw4: Middleware<
+ Mw4RequestContext,
+ Mw1RequestContext & Mw2RequestContext & Mw3RequestContext extends Mw4Dep
+ ? Mw4Dep
+ : never
+ >,
+ mw5: Middleware<
+ Mw5RequestContext,
+ Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext extends Mw5Dep
+ ? Mw5Dep
+ : never
+ >,
+ endpoint: (
+ req: Req & Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext &
+ Mw5RequestContext,
+ res: Res
+ ) => any
+) => (req: Req, res: Res) => any
+
+type Wrappers6 = <
+ Mw1RequestContext extends Mw2Dep,
+ Mw1Dep,
+ Mw2RequestContext,
+ Mw2Dep,
+ Mw3RequestContext,
+ Mw3Dep,
+ Mw4RequestContext,
+ Mw4Dep,
+ Mw5RequestContext,
+ Mw5Dep,
+ Mw6RequestContext,
+ Mw6Dep
+>(
+ mw1: Middleware,
+ mw2: Middleware,
+ mw3: Middleware<
+ Mw3RequestContext,
+ Mw1RequestContext & Mw2RequestContext extends Mw3Dep ? Mw3Dep : never
+ >,
+ mw4: Middleware<
+ Mw4RequestContext,
+ Mw1RequestContext & Mw2RequestContext & Mw3RequestContext extends Mw4Dep
+ ? Mw4Dep
+ : never
+ >,
+ mw5: Middleware<
+ Mw5RequestContext,
+ Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext extends Mw5Dep
+ ? Mw5Dep
+ : never
+ >,
+ mw6: Middleware<
+ Mw6RequestContext,
+ Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext &
+ Mw5RequestContext extends Mw6Dep
+ ? Mw6Dep
+ : never
+ >,
+ endpoint: (
+ req: Req & Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext &
+ Mw5RequestContext &
+ Mw6RequestContext,
+ res: Res
+ ) => any
+) => (req: Req, res: Res) => any
+
+type Wrappers7 = <
+ Mw1RequestContext extends Mw2Dep,
+ Mw1Dep,
+ Mw2RequestContext,
+ Mw2Dep,
+ Mw3RequestContext,
+ Mw3Dep,
+ Mw4RequestContext,
+ Mw4Dep,
+ Mw5RequestContext,
+ Mw5Dep,
+ Mw6RequestContext,
+ Mw6Dep,
+ Mw7RequestContext,
+ Mw7Dep
+>(
+ mw1: Middleware,
+ mw2: Middleware,
+ mw3: Middleware<
+ Mw3RequestContext,
+ Mw1RequestContext & Mw2RequestContext extends Mw3Dep ? Mw3Dep : never
+ >,
+ mw4: Middleware<
+ Mw4RequestContext,
+ Mw1RequestContext & Mw2RequestContext & Mw3RequestContext extends Mw4Dep
+ ? Mw4Dep
+ : never
+ >,
+ mw5: Middleware<
+ Mw5RequestContext,
+ Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext extends Mw5Dep
+ ? Mw5Dep
+ : never
+ >,
+ mw6: Middleware<
+ Mw6RequestContext,
+ Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext &
+ Mw5RequestContext extends Mw6Dep
+ ? Mw6Dep
+ : never
+ >,
+ mw7: Middleware<
+ Mw7RequestContext,
+ Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext &
+ Mw5RequestContext &
+ Mw6RequestContext extends Mw7Dep
+ ? Mw7Dep
+ : never
+ >,
+ endpoint: (
+ req: Req & Mw1RequestContext &
+ Mw2RequestContext &
+ Mw3RequestContext &
+ Mw4RequestContext &
+ Mw5RequestContext &
+ Mw6RequestContext &
+ Mw7RequestContext,
+ res: Res
+ ) => any
+) => (req: Req, res: Res) => any
+
+type Wrappers = Wrappers1 &
+ Wrappers2 &
+ Wrappers3 &
+ Wrappers4 &
+ Wrappers5 &
+ Wrappers6 &
+ Wrappers7
+
+export const wrappers: Wrappers = (...wrappersArgs: any[]) => {
+ const wrappedFunction = wrappersArgs[wrappersArgs.length - 1]
+ const mws = wrappersArgs.slice(0, -1)
+
+ let lastWrappedFunction = wrappedFunction
+ for (let i = mws.length - 1; i >= 0; i--) {
+ lastWrappedFunction = (mws[i] as any)(lastWrappedFunction)
+ }
+
+ return lastWrappedFunction
+}
+
+export default wrappers
\ No newline at end of file
diff --git a/packages/nextlove/src/zod-helpers.ts b/packages/nextlove/src/zod-helpers.ts
new file mode 100644
index 000000000..8418771c3
--- /dev/null
+++ b/packages/nextlove/src/zod-helpers.ts
@@ -0,0 +1,101 @@
+import { z, ZodFirstPartyTypeKind } from "zod"
+
+export const getZodObjectSchemaFromZodEffectSchema = (
+ isZodEffect: boolean,
+ schema: z.ZodTypeAny
+): z.ZodTypeAny | z.ZodObject => {
+ if (!isZodEffect) {
+ return schema as z.ZodObject
+ }
+
+ let currentSchema = schema
+
+ while (currentSchema instanceof z.ZodEffects) {
+ currentSchema = currentSchema._def.schema
+ }
+
+ return currentSchema as z.ZodObject
+}
+
+/**
+ * This function is used to get the correct schema from a ZodEffect | ZodDefault | ZodOptional schema.
+ * TODO: this function should handle all special cases of ZodSchema and not just ZodEffect | ZodDefault | ZodOptional
+ */
+export const getZodDefFromZodSchemaHelpers = (schema: z.ZodTypeAny) => {
+ const special_zod_types = [
+ ZodFirstPartyTypeKind.ZodOptional,
+ ZodFirstPartyTypeKind.ZodDefault,
+ ZodFirstPartyTypeKind.ZodEffects,
+ ]
+
+ while (special_zod_types.includes(schema._def.typeName)) {
+ if (
+ schema._def.typeName === ZodFirstPartyTypeKind.ZodOptional ||
+ schema._def.typeName === ZodFirstPartyTypeKind.ZodDefault
+ ) {
+ schema = schema._def.innerType
+ continue
+ }
+
+ if (schema._def.typeName === ZodFirstPartyTypeKind.ZodEffects) {
+ schema = schema._def.schema
+ continue
+ }
+ }
+ return schema._def
+}
+
+export const parseQueryParams = (
+ schema: z.ZodTypeAny,
+ input: Record
+) => {
+ const parsed_input = Object.assign({}, input)
+ const isZodEffect = schema._def.typeName === ZodFirstPartyTypeKind.ZodEffects
+ const safe_schema = getZodObjectSchemaFromZodEffectSchema(isZodEffect, schema)
+ const isZodObject =
+ safe_schema._def.typeName === ZodFirstPartyTypeKind.ZodObject
+
+ if (isZodObject) {
+ const obj_schema = safe_schema as z.ZodObject
+
+ for (const [key, value] of Object.entries(obj_schema.shape)) {
+ const def = getZodDefFromZodSchemaHelpers(value as z.ZodTypeAny)
+ const isArray = def.typeName === ZodFirstPartyTypeKind.ZodArray
+ if (isArray) {
+ const array_input = input[key]
+
+ if (typeof array_input === "string") {
+ parsed_input[key] = array_input.split(",")
+ }
+
+ if (Array.isArray(input[`${key}[]`])) {
+ parsed_input[key] = input[`${key}[]`]
+ }
+
+ continue
+ }
+
+ const isBoolean = def.typeName === ZodFirstPartyTypeKind.ZodBoolean
+ if (isBoolean) {
+ const boolean_input = input[key]
+
+ if (typeof boolean_input === "string") {
+ parsed_input[key] = boolean_input === "true"
+ }
+ }
+ }
+ }
+
+ return schema.parse(parsed_input)
+}
+
+
+export const zodIssueToString = (issue: z.ZodIssue) => {
+ if (issue.path.join(".") === "") {
+ return issue.message
+ }
+ if (issue.message === "Required") {
+ return `${issue.path.join(".")} is required`
+ }
+ return `${issue.message} for "${issue.path.join(".")}"`
+}
diff --git a/packages/nextlove/tsconfig.json b/packages/nextlove/tsconfig.json
index b06c1fa83..3f2f72de3 100755
--- a/packages/nextlove/tsconfig.json
+++ b/packages/nextlove/tsconfig.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "target": "es2020",
+ "target": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
@@ -17,5 +17,5 @@
"incremental": false
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
- "exclude": ["node_modules"]
+ "exclude": ["node_modules", "dist"]
}
diff --git a/yarn.lock b/yarn.lock
index 164eacb0a..7a1138536 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -686,6 +686,11 @@
resolved "https://registry.yarnpkg.com/@next/env/-/env-12.2.0.tgz#17ce2d9f5532b677829840037e06f208b7eed66b"
integrity sha512-/FCkDpL/8SodJEXvx/DYNlOD5ijTtkozf4PPulYPtkPOJaMPpBSOkzmsta4fnrnbdH6eZjbwbiXFdr6gSQCV4w==
+"@next/env@13.4.9":
+ version "13.4.9"
+ resolved "https://registry.yarnpkg.com/@next/env/-/env-13.4.9.tgz#b77759514dd56bfa9791770755a2482f4d6ca93e"
+ integrity sha512-vuDRK05BOKfmoBYLNi2cujG2jrYbEod/ubSSyqgmEx9n/W3eZaJQdRNhTfumO+qmq/QTzLurW487n/PM/fHOkw==
+
"@next/eslint-plugin-next@12.2.0":
version "12.2.0"
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-12.2.0.tgz#38b36d3be244cc9a98c0e7d203bdb062f87df4ac"
@@ -715,11 +720,21 @@
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.2.0.tgz#3473889157ba70b30ccdd4f59c46232d841744e2"
integrity sha512-x5U5gJd7ZvrEtTFnBld9O2bUlX8opu7mIQUqRzj7KeWzBwPhrIzTTsQXAiNqsaMuaRPvyHBVW/5d/6g6+89Y8g==
+"@next/swc-darwin-arm64@13.4.9":
+ version "13.4.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.9.tgz#0ed408d444bbc6b0a20f3506a9b4222684585677"
+ integrity sha512-TVzGHpZoVBk3iDsTOQA/R6MGmFp0+17SWXMEWd6zG30AfuELmSSMe2SdPqxwXU0gbpWkJL1KgfLzy5ReN0crqQ==
+
"@next/swc-darwin-x64@12.2.0":
version "12.2.0"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.2.0.tgz#b25198c3ef4c906000af49e4787a757965f760bb"
integrity sha512-iwMNFsrAPjfedjKDv9AXPAV16PWIomP3qw/FfPaxkDVRbUls7BNdofBLzkQmqxqWh93WrawLwaqyXpJuAaiwJA==
+"@next/swc-darwin-x64@13.4.9":
+ version "13.4.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.9.tgz#a08fccdee68201522fe6618ec81f832084b222f8"
+ integrity sha512-aSfF1fhv28N2e7vrDZ6zOQ+IIthocfaxuMWGReB5GDriF0caTqtHttAvzOMgJgXQtQx6XhyaJMozLTSEXeNN+A==
+
"@next/swc-freebsd-x64@12.2.0":
version "12.2.0"
resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.2.0.tgz#78e2213f8b703be0fef23a49507779b4a9842929"
@@ -735,36 +750,71 @@
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.2.0.tgz#134a42ddea804d6bf04761607f774432c3126de6"
integrity sha512-++WAB4ElXCSOKG9H8r4ENF8EaV+w0QkrpjehmryFkQXmt5juVXz+nKDVlCRMwJU7A1O0Mie82XyEoOrf6Np1pA==
+"@next/swc-linux-arm64-gnu@13.4.9":
+ version "13.4.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.9.tgz#1798c2341bb841e96521433eed00892fb24abbd1"
+ integrity sha512-JhKoX5ECzYoTVyIy/7KykeO4Z2lVKq7HGQqvAH+Ip9UFn1MOJkOnkPRB7v4nmzqAoY+Je05Aj5wNABR1N18DMg==
+
"@next/swc-linux-arm64-musl@12.2.0":
version "12.2.0"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.2.0.tgz#c781ac642ad35e0578d8a8d19c638b0f31c1a334"
integrity sha512-XrqkHi/VglEn5zs2CYK6ofJGQySrd+Lr4YdmfJ7IhsCnMKkQY1ma9Hv5THwhZVof3e+6oFHrQ9bWrw9K4WTjFA==
+"@next/swc-linux-arm64-musl@13.4.9":
+ version "13.4.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.9.tgz#cee04c51610eddd3638ce2499205083656531ea0"
+ integrity sha512-OOn6zZBIVkm/4j5gkPdGn4yqQt+gmXaLaSjRSO434WplV8vo2YaBNbSHaTM9wJpZTHVDYyjzuIYVEzy9/5RVZw==
+
"@next/swc-linux-x64-gnu@12.2.0":
version "12.2.0"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.2.0.tgz#0e2235a59429eadd40ac8880aec18acdbc172a31"
integrity sha512-MyhHbAKVjpn065WzRbqpLu2krj4kHLi6RITQdD1ee+uxq9r2yg5Qe02l24NxKW+1/lkmpusl4Y5Lks7rBiJn4w==
+"@next/swc-linux-x64-gnu@13.4.9":
+ version "13.4.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.9.tgz#1932d0367916adbc6844b244cda1d4182bd11f7a"
+ integrity sha512-iA+fJXFPpW0SwGmx/pivVU+2t4zQHNOOAr5T378PfxPHY6JtjV6/0s1vlAJUdIHeVpX98CLp9k5VuKgxiRHUpg==
+
"@next/swc-linux-x64-musl@12.2.0":
version "12.2.0"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.2.0.tgz#b0a10db0d9e16f079429588a58f71fa3c3d46178"
integrity sha512-Tz1tJZ5egE0S/UqCd5V6ZPJsdSzv/8aa7FkwFmIJ9neLS8/00za+OY5pq470iZQbPrkTwpKzmfTTIPRVD5iqDg==
+"@next/swc-linux-x64-musl@13.4.9":
+ version "13.4.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.9.tgz#a66aa8c1383b16299b72482f6360facd5cde3c7a"
+ integrity sha512-rlNf2WUtMM+GAQrZ9gMNdSapkVi3koSW3a+dmBVp42lfugWVvnyzca/xJlN48/7AGx8qu62WyO0ya1ikgOxh6A==
+
"@next/swc-win32-arm64-msvc@12.2.0":
version "12.2.0"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.2.0.tgz#3063f850c9db7b774c69e9be74ad59986cf6fc34"
integrity sha512-0iRO/CPMCdCYUzuH6wXLnsfJX1ykBX4emOOvH0qIgtiZM0nVYbF8lkEyY2ph4XcsurpinS+ziWuYCXVqrOSqiw==
+"@next/swc-win32-arm64-msvc@13.4.9":
+ version "13.4.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.9.tgz#39482ee856c867177a612a30b6861c75e0736a4a"
+ integrity sha512-5T9ybSugXP77nw03vlgKZxD99AFTHaX8eT1ayKYYnGO9nmYhJjRPxcjU5FyYI+TdkQgEpIcH7p/guPLPR0EbKA==
+
"@next/swc-win32-ia32-msvc@12.2.0":
version "12.2.0"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.2.0.tgz#001bbadf3d2cf006c4991f728d1d23e4d5c0e7cc"
integrity sha512-8A26RJVcJHwIKm8xo/qk2ePRquJ6WCI2keV2qOW/Qm+ZXrPXHMIWPYABae/nKN243YFBNyPiHytjX37VrcpUhg==
+"@next/swc-win32-ia32-msvc@13.4.9":
+ version "13.4.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.9.tgz#29db85e34b597ade1a918235d16a760a9213c190"
+ integrity sha512-ojZTCt1lP2ucgpoiFgrFj07uq4CZsq4crVXpLGgQfoFq00jPKRPgesuGPaz8lg1yLfvafkU3Jd1i8snKwYR3LA==
+
"@next/swc-win32-x64-msvc@12.2.0":
version "12.2.0"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.2.0.tgz#9f66664f9122ca555b96a5f2fc6e2af677bf801b"
integrity sha512-OI14ozFLThEV3ey6jE47zrzSTV/6eIMsvbwozo+XfdWqOPwQ7X00YkRx4GVMKMC0rM44oGS2gmwMKYpe4EblnA==
+"@next/swc-win32-x64-msvc@13.4.9":
+ version "13.4.9"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.9.tgz#0c2758164cccd61bc5a1c6cd8284fe66173e4a2b"
+ integrity sha512-QbT03FXRNdpuL+e9pLnu+XajZdm/TtIXVYY4lA9t+9l0fLZbHXDYEKitAqxrOj37o3Vx5ufxiRAniaIebYDCgw==
+
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -1271,6 +1321,13 @@
dependencies:
tslib "^2.4.0"
+"@swc/helpers@0.5.1":
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.1.tgz#e9031491aa3f26bfcc974a67f48bd456c8a5357a"
+ integrity sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==
+ dependencies:
+ tslib "^2.4.0"
+
"@swc/jest@^0.2.24":
version "0.2.24"
resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.24.tgz#35d9377ede049613cd5fdd6c24af2b8dcf622875"
@@ -2165,6 +2222,13 @@ bundle-require@^3.0.2:
dependencies:
load-tsconfig "^0.2.0"
+busboy@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
+ integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
+ dependencies:
+ streamsearch "^1.1.0"
+
bytes@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
@@ -2259,6 +2323,11 @@ caniuse-lite@^1.0.30001332:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001464.tgz#888922718df48ce5e33dcfe1a2af7d42676c5eb7"
integrity sha512-oww27MtUmusatpRpCGSOneQk2/l5czXANDSFvsc7VuOQ86s3ANhZetpwXNf1zY/zdfP63Xvjz325DAdAoES13g==
+caniuse-lite@^1.0.30001406:
+ version "1.0.30001515"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001515.tgz#418aefeed9d024cd3129bfae0ccc782d4cb8f12b"
+ integrity sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA==
+
caniuse-lite@^1.0.30001449:
version "1.0.30001470"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001470.tgz#09c8e87c711f75ff5d39804db2613dd593feeb10"
@@ -2410,6 +2479,11 @@ cli-truncate@^2.1.0:
slice-ansi "^3.0.0"
string-width "^4.2.0"
+client-only@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
+ integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
+
cliui@^7.0.2:
version "7.0.4"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
@@ -4027,6 +4101,11 @@ glob-promise@4.2.2, glob-promise@^4.2.2:
dependencies:
"@types/glob" "^7.1.3"
+glob-to-regexp@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
+ integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
+
glob@7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
@@ -6022,6 +6101,11 @@ nanoid@^3.1.30:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
+nanoid@^3.3.4:
+ version "3.3.6"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
+ integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
+
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -6073,6 +6157,30 @@ next@12.2.0:
"@next/swc-win32-ia32-msvc" "12.2.0"
"@next/swc-win32-x64-msvc" "12.2.0"
+next@^13.4.9:
+ version "13.4.9"
+ resolved "https://registry.yarnpkg.com/next/-/next-13.4.9.tgz#473de5997cb4c5d7a4fb195f566952a1cbffbeba"
+ integrity sha512-vtefFm/BWIi/eWOqf1GsmKG3cjKw1k3LjuefKRcL3iiLl3zWzFdPG3as6xtxrGO6gwTzzaO1ktL4oiHt/uvTjA==
+ dependencies:
+ "@next/env" "13.4.9"
+ "@swc/helpers" "0.5.1"
+ busboy "1.6.0"
+ caniuse-lite "^1.0.30001406"
+ postcss "8.4.14"
+ styled-jsx "5.1.1"
+ watchpack "2.4.0"
+ zod "3.21.4"
+ optionalDependencies:
+ "@next/swc-darwin-arm64" "13.4.9"
+ "@next/swc-darwin-x64" "13.4.9"
+ "@next/swc-linux-arm64-gnu" "13.4.9"
+ "@next/swc-linux-arm64-musl" "13.4.9"
+ "@next/swc-linux-x64-gnu" "13.4.9"
+ "@next/swc-linux-x64-musl" "13.4.9"
+ "@next/swc-win32-arm64-msvc" "13.4.9"
+ "@next/swc-win32-ia32-msvc" "13.4.9"
+ "@next/swc-win32-x64-msvc" "13.4.9"
+
nextjs-exception-middleware@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/nextjs-exception-middleware/-/nextjs-exception-middleware-2.0.1.tgz#45b3254cdb7deee583337ff04672109083b41938"
@@ -6857,6 +6965,15 @@ postcss-selector-parser@^6.0.10:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
+postcss@8.4.14:
+ version "8.4.14"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf"
+ integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
+ dependencies:
+ nanoid "^3.3.4"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
postcss@8.4.5:
version "8.4.5"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95"
@@ -7051,7 +7168,7 @@ rc@1.2.8, rc@^1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-dom@18.2.0:
+react-dom@18.2.0, react-dom@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
@@ -7069,7 +7186,7 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
-react@18.2.0:
+react@18.2.0, react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
@@ -7598,7 +7715,7 @@ socks@^2.6.2:
ip "^2.0.0"
smart-buffer "^4.2.0"
-source-map-js@^1.0.1:
+source-map-js@^1.0.1, source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
@@ -7722,6 +7839,11 @@ stream-combiner2@~1.1.1:
duplexer2 "~0.1.0"
readable-stream "^2.0.2"
+streamsearch@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
+ integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
+
string-length@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
@@ -7836,6 +7958,13 @@ styled-jsx@5.0.2:
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.2.tgz#ff230fd593b737e9e68b630a694d460425478729"
integrity sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==
+styled-jsx@5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f"
+ integrity sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==
+ dependencies:
+ client-only "0.0.1"
+
sucrase@^3.20.3:
version "3.29.0"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.29.0.tgz#3207c5bc1b980fdae1e539df3f8a8a518236da7d"
@@ -8409,6 +8538,14 @@ walker@^1.0.8:
dependencies:
makeerror "1.0.12"
+watchpack@2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
+ integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
+ dependencies:
+ glob-to-regexp "^0.4.1"
+ graceful-fs "^4.1.2"
+
wcwidth@^1.0.0, wcwidth@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
@@ -8625,7 +8762,7 @@ zod-to-ts@^1.1.1:
resolved "https://registry.yarnpkg.com/zod-to-ts/-/zod-to-ts-1.1.2.tgz#d505e46f99edd50a2e53e6dcd156494d2ef82a40"
integrity sha512-IZdM0Ga1l/vQnrNy63aV3qsLNchD0aUYZEtXcyt8A3xA7+erFO0zeImEcUH5qNLA6tXDTjRpcWp0paoVoH/D0A==
-zod@^3.17.3:
+zod@3.21.4, zod@^3.17.3:
version "3.21.4"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db"
integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==