feat: Replace existing router with the new implementation (#11646)
This commit is contained in:
@@ -16,7 +16,7 @@ import { container } from "../../../container"
|
||||
import { featureFlagsLoader } from "../../../feature-flags"
|
||||
import { logger } from "../../../logger"
|
||||
import { MedusaRequest } from "../../types"
|
||||
import { RoutesLoader } from "../../router"
|
||||
import { ApiLoader } from "../../router"
|
||||
|
||||
function asArray(resolvers) {
|
||||
return {
|
||||
@@ -94,7 +94,7 @@ export const createServer = async (rootDir) => {
|
||||
next()
|
||||
})
|
||||
|
||||
await new RoutesLoader({
|
||||
await new ApiLoader({
|
||||
app,
|
||||
sourceDir: rootDir,
|
||||
}).load()
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
storeGlobalMiddlewareMock,
|
||||
} from "../__fixtures__/mocks"
|
||||
import { createServer } from "../__fixtures__/server"
|
||||
import { MedusaNextFunction, RoutesLoader } from "../index"
|
||||
import { MedusaNextFunction, ApiLoader } from "../index"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
@@ -237,7 +237,7 @@ describe("RoutesLoader", function () {
|
||||
__dirname,
|
||||
"../__fixtures__/routers-duplicate-parameter"
|
||||
)
|
||||
const err = await new RoutesLoader({
|
||||
const err = await new ApiLoader({
|
||||
app,
|
||||
sourceDir: rootDir,
|
||||
})
|
||||
@@ -246,7 +246,7 @@ describe("RoutesLoader", function () {
|
||||
|
||||
expect(err).toBeDefined()
|
||||
expect(err.message).toBe(
|
||||
"Duplicate parameters found in route /admin/customers/[id]/orders/[id] (id). Make sure that all parameters are unique."
|
||||
"Duplicate parameters found in route /admin/customers/[id]/orders/[id]/route.ts (id). Make sure that all parameters are unique."
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import { MiddlewareFileLoader } from "../middleware-file-loader"
|
||||
describe("Middleware file loader", () => {
|
||||
it("should load routes from the filesystem", async () => {
|
||||
const BASE_DIR = resolve(__dirname, "../__fixtures__/routers-middleware")
|
||||
const loader = new MiddlewareFileLoader({})
|
||||
const loader = new MiddlewareFileLoader()
|
||||
await loader.scanDir(BASE_DIR)
|
||||
|
||||
expect(loader.getBodyParserConfigRoutes()).toMatchInlineSnapshot(`
|
||||
@@ -14,12 +14,22 @@ describe("Middleware file loader", () => {
|
||||
"preserveRawBody": true,
|
||||
},
|
||||
"matcher": "/webhooks",
|
||||
"method": undefined,
|
||||
"methods": [
|
||||
"GET",
|
||||
"POST",
|
||||
"PUT",
|
||||
"PATCH",
|
||||
"DELETE",
|
||||
"OPTIONS",
|
||||
"HEAD",
|
||||
],
|
||||
},
|
||||
{
|
||||
"config": false,
|
||||
"matcher": "/webhooks/*",
|
||||
"method": "POST",
|
||||
"methods": [
|
||||
"POST",
|
||||
],
|
||||
},
|
||||
]
|
||||
`)
|
||||
@@ -28,22 +38,26 @@ describe("Middleware file loader", () => {
|
||||
{
|
||||
"handler": [Function],
|
||||
"matcher": "/customers",
|
||||
"method": undefined,
|
||||
"methods": undefined,
|
||||
},
|
||||
{
|
||||
"handler": [Function],
|
||||
"matcher": "/customers",
|
||||
"method": "POST",
|
||||
"methods": [
|
||||
"POST",
|
||||
],
|
||||
},
|
||||
{
|
||||
"handler": [Function],
|
||||
"matcher": "/store/*",
|
||||
"method": undefined,
|
||||
"methods": undefined,
|
||||
},
|
||||
{
|
||||
"handler": [Function],
|
||||
"matcher": "/webhooks/*",
|
||||
"method": "POST",
|
||||
"methods": [
|
||||
"POST",
|
||||
],
|
||||
},
|
||||
]
|
||||
`)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { RoutesLoader } from "../routes-loader"
|
||||
describe("Routes loader", () => {
|
||||
it("should load routes from the filesystem", async () => {
|
||||
const BASE_DIR = resolve(__dirname, "../__fixtures__/routers")
|
||||
const loader = new RoutesLoader({})
|
||||
const loader = new RoutesLoader()
|
||||
await loader.scanDir(BASE_DIR)
|
||||
|
||||
expect(loader.getRoutes()).toMatchInlineSnapshot(`
|
||||
@@ -12,10 +12,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/orders/[id]/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/orders/:id",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/orders/[id]/route.ts",
|
||||
"route": "/admin/orders/:id",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -23,10 +24,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/orders/[id]/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/orders/:id",
|
||||
"method": "POST",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/orders/[id]/route.ts",
|
||||
"route": "/admin/orders/:id",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -34,10 +36,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/orders/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/orders",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/orders/route.ts",
|
||||
"route": "/admin/orders",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -45,10 +48,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/products/[id]/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products/:id",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/products/[id]/route.ts",
|
||||
"route": "/admin/products/:id",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -56,10 +60,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "DELETE",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -67,10 +72,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -78,10 +84,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "HEAD",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -89,10 +96,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "OPTIONS",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -100,10 +108,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "PATCH",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -111,10 +120,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "POST",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -122,10 +132,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "PUT",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -133,10 +144,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/route.ts",
|
||||
"route": "/admin",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -144,10 +156,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin",
|
||||
"method": "POST",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/route.ts",
|
||||
"route": "/admin",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -155,10 +168,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/customers/[customer_id]/orders/[order_id]/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/customers/:customer_id/orders/:order_id",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/customers/[customer_id]/orders/[order_id]/route.ts",
|
||||
"route": "/customers/:customer_id/orders/:order_id",
|
||||
"shouldAppendAdminCors": false,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -166,10 +180,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/customers/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/customers",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/customers/route.ts",
|
||||
"route": "/customers",
|
||||
"shouldAppendAdminCors": false,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -184,7 +199,7 @@ describe("Routes loader", () => {
|
||||
__dirname,
|
||||
"../__fixtures__/routers-with-duplicates"
|
||||
)
|
||||
const loader = new RoutesLoader({})
|
||||
const loader = new RoutesLoader()
|
||||
await loader.scanDir(BASE_DIR)
|
||||
await loader.scanDir(BASE_DIR_2)
|
||||
|
||||
@@ -193,10 +208,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/orders/[id]/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/orders/:id",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/orders/[id]/route.ts",
|
||||
"route": "/admin/orders/:id",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -204,10 +220,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/orders/[id]/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/orders/:id",
|
||||
"method": "POST",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/orders/[id]/route.ts",
|
||||
"route": "/admin/orders/:id",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -215,10 +232,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/orders/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/orders",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/orders/route.ts",
|
||||
"route": "/admin/orders",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -226,10 +244,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR_2}/admin/products/[id]/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products/:id",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": true,
|
||||
"relativePath": "/admin/products/[id]/route.ts",
|
||||
"route": "/admin/products/:id",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -237,10 +256,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "DELETE",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -248,10 +268,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR_2}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": true,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -259,10 +280,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "HEAD",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -270,10 +292,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "OPTIONS",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -281,10 +304,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "PATCH",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -292,10 +316,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR_2}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "POST",
|
||||
"optedOutOfAuth": true,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -303,10 +328,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/products/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin/products",
|
||||
"method": "PUT",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/products/route.ts",
|
||||
"route": "/admin/products",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -314,10 +340,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/route.ts",
|
||||
"route": "/admin",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -325,10 +352,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/admin/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/admin",
|
||||
"method": "POST",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/admin/route.ts",
|
||||
"route": "/admin",
|
||||
"shouldAppendAdminCors": true,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -336,10 +364,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/customers/[customer_id]/orders/[order_id]/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/customers/:customer_id/orders/:order_id",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/customers/[customer_id]/orders/[order_id]/route.ts",
|
||||
"route": "/customers/:customer_id/orders/:order_id",
|
||||
"shouldAppendAdminCors": false,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -347,10 +376,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR}/customers/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/customers",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/customers/route.ts",
|
||||
"route": "/customers",
|
||||
"shouldAppendAdminCors": false,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": false,
|
||||
@@ -358,10 +388,11 @@ describe("Routes loader", () => {
|
||||
{
|
||||
"absolutePath": "${BASE_DIR_2}/store/[customer_id]/orders/[order_id]/route.ts",
|
||||
"handler": [Function],
|
||||
"isRoute": true,
|
||||
"matcher": "/store/:customer_id/orders/:order_id",
|
||||
"method": "GET",
|
||||
"optedOutOfAuth": false,
|
||||
"relativePath": "/store/[customer_id]/orders/[order_id]/route.ts",
|
||||
"route": "/store/:customer_id/orders/:order_id",
|
||||
"shouldAppendAdminCors": false,
|
||||
"shouldAppendAuthCors": false,
|
||||
"shouldAppendStoreCors": true,
|
||||
@@ -376,7 +407,7 @@ describe("Routes loader", () => {
|
||||
"../__fixtures__/routers-duplicate-parameter"
|
||||
)
|
||||
|
||||
const loader = new RoutesLoader({})
|
||||
const loader = new RoutesLoader()
|
||||
await expect(() => loader.scanDir(BASE_DIR)).rejects.toThrow(
|
||||
"Duplicate parameters found in route /admin/customers/[id]/orders/[id]/route.ts (id). Make sure that all parameters are unique."
|
||||
)
|
||||
|
||||
@@ -19,6 +19,14 @@ describe("Routes sorter", () => {
|
||||
isAppRoute: true,
|
||||
handler: () => {},
|
||||
},
|
||||
{
|
||||
matcher: "/admin",
|
||||
handler: () => {},
|
||||
},
|
||||
{
|
||||
matcher: "/store",
|
||||
handler: () => {},
|
||||
},
|
||||
{
|
||||
matcher: "/admin/products/export",
|
||||
methods: ["GET"],
|
||||
@@ -32,12 +40,20 @@ describe("Routes sorter", () => {
|
||||
},
|
||||
{
|
||||
matcher: "/admin/products/:id",
|
||||
isAppRoute: true,
|
||||
methods: ["GET"],
|
||||
handler: () => {},
|
||||
},
|
||||
{
|
||||
matcher: "/admin/products/:id",
|
||||
isAppRoute: true,
|
||||
methods: ["POST"],
|
||||
handler: () => {},
|
||||
},
|
||||
{
|
||||
matcher: "/admin/products/batch",
|
||||
methods: ["GET"],
|
||||
isAppRoute: true,
|
||||
methods: ["POST"],
|
||||
handler: () => {},
|
||||
},
|
||||
{
|
||||
@@ -57,6 +73,7 @@ describe("Routes sorter", () => {
|
||||
},
|
||||
{
|
||||
matcher: "/admin/:id/export",
|
||||
isAppRoute: true,
|
||||
methods: ["GET"],
|
||||
handler: () => {},
|
||||
},
|
||||
@@ -69,6 +86,14 @@ describe("Routes sorter", () => {
|
||||
|
||||
expect(sorter.sort()).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"handler": [Function],
|
||||
"matcher": "/admin",
|
||||
},
|
||||
{
|
||||
"handler": [Function],
|
||||
"matcher": "/store",
|
||||
},
|
||||
{
|
||||
"handler": [Function],
|
||||
"matcher": "/v1",
|
||||
@@ -136,13 +161,15 @@ describe("Routes sorter", () => {
|
||||
},
|
||||
{
|
||||
"handler": [Function],
|
||||
"isAppRoute": true,
|
||||
"matcher": "/admin/products/batch",
|
||||
"methods": [
|
||||
"GET",
|
||||
"POST",
|
||||
],
|
||||
},
|
||||
{
|
||||
"handler": [Function],
|
||||
"isAppRoute": true,
|
||||
"matcher": "/admin/products/:id",
|
||||
"methods": [
|
||||
"GET",
|
||||
@@ -150,6 +177,15 @@ describe("Routes sorter", () => {
|
||||
},
|
||||
{
|
||||
"handler": [Function],
|
||||
"isAppRoute": true,
|
||||
"matcher": "/admin/products/:id",
|
||||
"methods": [
|
||||
"POST",
|
||||
],
|
||||
},
|
||||
{
|
||||
"handler": [Function],
|
||||
"isAppRoute": true,
|
||||
"matcher": "/admin/:id/export",
|
||||
"methods": [
|
||||
"GET",
|
||||
@@ -286,4 +322,77 @@ describe("Routes sorter", () => {
|
||||
]
|
||||
`)
|
||||
})
|
||||
|
||||
it("should sort routes with multiple params and static values", () => {
|
||||
const sorter = new RoutesSorter([
|
||||
{
|
||||
matcher: "/admin/promotions/:id/:rule_type",
|
||||
methods: ["GET"],
|
||||
handler: () => {},
|
||||
},
|
||||
{
|
||||
matcher:
|
||||
"/admin/promotions/rule-value-options/:rule_type/:rule_attribute_id",
|
||||
methods: ["GET"],
|
||||
handler: () => {},
|
||||
},
|
||||
{
|
||||
matcher: "/admin/promotions/rule-attribute-options/:rule_type",
|
||||
methods: ["GET"],
|
||||
handler: () => {},
|
||||
},
|
||||
{
|
||||
matcher: "/admin/promotions/:id/:rule_type",
|
||||
methods: ["GET"],
|
||||
handler: () => {},
|
||||
},
|
||||
{
|
||||
matcher: "/admin/promotions/rule-attribute-options/:rule_type",
|
||||
methods: ["GET"],
|
||||
isAppRoute: true,
|
||||
handler: () => {},
|
||||
},
|
||||
])
|
||||
|
||||
expect(sorter.sort()).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"handler": [Function],
|
||||
"matcher": "/admin/promotions/rule-value-options/:rule_type/:rule_attribute_id",
|
||||
"methods": [
|
||||
"GET",
|
||||
],
|
||||
},
|
||||
{
|
||||
"handler": [Function],
|
||||
"matcher": "/admin/promotions/rule-attribute-options/:rule_type",
|
||||
"methods": [
|
||||
"GET",
|
||||
],
|
||||
},
|
||||
{
|
||||
"handler": [Function],
|
||||
"isAppRoute": true,
|
||||
"matcher": "/admin/promotions/rule-attribute-options/:rule_type",
|
||||
"methods": [
|
||||
"GET",
|
||||
],
|
||||
},
|
||||
{
|
||||
"handler": [Function],
|
||||
"matcher": "/admin/promotions/:id/:rule_type",
|
||||
"methods": [
|
||||
"GET",
|
||||
],
|
||||
},
|
||||
{
|
||||
"handler": [Function],
|
||||
"matcher": "/admin/promotions/:id/:rule_type",
|
||||
"methods": [
|
||||
"GET",
|
||||
],
|
||||
},
|
||||
]
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,10 +2,12 @@ import { join } from "path"
|
||||
import { dynamicImport, FileSystem } from "@medusajs/utils"
|
||||
|
||||
import { logger } from "../logger"
|
||||
import type {
|
||||
MiddlewaresConfig,
|
||||
BodyParserConfigRoute,
|
||||
ScannedMiddlewareDescriptor,
|
||||
import {
|
||||
type MiddlewaresConfig,
|
||||
type BodyParserConfigRoute,
|
||||
type MiddlewareDescriptor,
|
||||
type MedusaErrorHandlerFunction,
|
||||
HTTP_METHODS,
|
||||
} from "./types"
|
||||
|
||||
/**
|
||||
@@ -13,21 +15,6 @@ import type {
|
||||
*/
|
||||
const MIDDLEWARE_FILE_NAME = "middlewares"
|
||||
|
||||
const log = ({
|
||||
activityId,
|
||||
message,
|
||||
}: {
|
||||
activityId?: string
|
||||
message: string
|
||||
}) => {
|
||||
if (activityId) {
|
||||
logger.progress(activityId, message)
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the API to scan a directory and load the `middleware.ts` file. This file contains
|
||||
* the configuration for certain global middlewares and core routes validators. Also, it may
|
||||
@@ -35,19 +22,19 @@ const log = ({
|
||||
*/
|
||||
export class MiddlewareFileLoader {
|
||||
/**
|
||||
* Middleware collected manually or by scanning directories
|
||||
* Global error handler exported from the middleware file loader
|
||||
*/
|
||||
#middleware: ScannedMiddlewareDescriptor[] = []
|
||||
#bodyParserConfigRoutes: BodyParserConfigRoute[] = []
|
||||
#errorHandler?: MedusaErrorHandlerFunction
|
||||
|
||||
/**
|
||||
* An eventual activity id for information tracking
|
||||
* Middleware collected manually or by scanning directories
|
||||
*/
|
||||
readonly #activityId?: string
|
||||
#middleware: MiddlewareDescriptor[] = []
|
||||
|
||||
constructor({ activityId }: { activityId?: string }) {
|
||||
this.#activityId = activityId
|
||||
}
|
||||
/**
|
||||
* Route matchers on which a custom body parser config is used
|
||||
*/
|
||||
#bodyParserConfigRoutes: BodyParserConfigRoute[] = []
|
||||
|
||||
/**
|
||||
* Processes the middleware file and returns the middleware and the
|
||||
@@ -58,25 +45,23 @@ export class MiddlewareFileLoader {
|
||||
|
||||
const middlewareConfig = middlewareExports.default
|
||||
if (!middlewareConfig) {
|
||||
log({
|
||||
activityId: this.#activityId,
|
||||
message: `No middleware configuration found in ${absolutePath}. Skipping middleware configuration.`,
|
||||
})
|
||||
logger.warn(
|
||||
`No middleware configuration found in ${absolutePath}. Skipping middleware configuration.`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const routes = middlewareConfig.routes as MiddlewaresConfig["routes"]
|
||||
if (!routes || !Array.isArray(routes)) {
|
||||
log({
|
||||
activityId: this.#activityId,
|
||||
message: `Invalid default export found in ${absolutePath}. Make sure to use "defineMiddlewares" function and export its output.`,
|
||||
})
|
||||
logger.warn(
|
||||
`Invalid default export found in ${absolutePath}. Make sure to use "defineMiddlewares" function and export its output.`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const result = routes.reduce<{
|
||||
bodyParserConfigRoutes: BodyParserConfigRoute[]
|
||||
middleware: ScannedMiddlewareDescriptor[]
|
||||
middleware: MiddlewareDescriptor[]
|
||||
}>(
|
||||
(result, route) => {
|
||||
if (!route.matcher) {
|
||||
@@ -92,9 +77,15 @@ export class MiddlewareFileLoader {
|
||||
const matcher = String(route.matcher)
|
||||
|
||||
if ("bodyParser" in route && route.bodyParser !== undefined) {
|
||||
const methods = route.methods || [...HTTP_METHODS]
|
||||
|
||||
logger.debug(
|
||||
`using custom bodyparser config on matcher ${methods}:${route.matcher}`
|
||||
)
|
||||
|
||||
result.bodyParserConfigRoutes.push({
|
||||
matcher: matcher,
|
||||
method: route.method,
|
||||
methods,
|
||||
config: route.bodyParser,
|
||||
})
|
||||
}
|
||||
@@ -104,7 +95,7 @@ export class MiddlewareFileLoader {
|
||||
result.middleware.push({
|
||||
handler: middleware,
|
||||
matcher: matcher,
|
||||
method: route.method,
|
||||
methods: route.methods,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -116,8 +107,16 @@ export class MiddlewareFileLoader {
|
||||
}
|
||||
)
|
||||
|
||||
this.#middleware = result.middleware
|
||||
this.#bodyParserConfigRoutes = result.bodyParserConfigRoutes
|
||||
const errorHandler =
|
||||
middlewareConfig.errorHandler as MiddlewaresConfig["errorHandler"]
|
||||
|
||||
if (errorHandler) {
|
||||
this.#errorHandler = errorHandler
|
||||
}
|
||||
this.#middleware = this.#middleware.concat(result.middleware)
|
||||
this.#bodyParserConfigRoutes = this.#bodyParserConfigRoutes.concat(
|
||||
result.bodyParserConfigRoutes
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,11 +132,18 @@ export class MiddlewareFileLoader {
|
||||
)
|
||||
} else if (await fs.exists(`${MIDDLEWARE_FILE_NAME}.js`)) {
|
||||
await this.#processMiddlewareFile(
|
||||
join(sourceDir, `${MIDDLEWARE_FILE_NAME}.ts`)
|
||||
join(sourceDir, `${MIDDLEWARE_FILE_NAME}.js`)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the globally registered error handler (if any)
|
||||
*/
|
||||
getErrorHandler() {
|
||||
return this.#errorHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of registered middleware
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { isObject, isPresent } from "@medusajs/utils"
|
||||
import { MedusaNextFunction, MedusaRequest } from "../types"
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../types"
|
||||
|
||||
export function applyDefaultFilters<TFilter extends object>(
|
||||
filtersToApply: TFilter
|
||||
) {
|
||||
return async (req: MedusaRequest, _, next: MedusaNextFunction) => {
|
||||
return async function defaultFiltersMiddleware(
|
||||
req: MedusaRequest,
|
||||
_: MedusaResponse,
|
||||
next: MedusaNextFunction
|
||||
) {
|
||||
for (const [filter, filterValue] of Object.entries(filtersToApply)) {
|
||||
let valueToApply = filterValue
|
||||
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { MedusaNextFunction, MedusaRequest } from "../types"
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../types"
|
||||
|
||||
export function applyParamsAsFilters(mappings: { [param: string]: string }) {
|
||||
return async (req: MedusaRequest, _, next: MedusaNextFunction) => {
|
||||
return async function paramsAsFiltersMiddleware(
|
||||
req: MedusaRequest,
|
||||
_: MedusaResponse,
|
||||
next: MedusaNextFunction
|
||||
) {
|
||||
for (const [param, paramValue] of Object.entries(req.params)) {
|
||||
if (mappings[param]) {
|
||||
req.filterableFields[mappings[param]] = paramValue
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { memoize } from "lodash"
|
||||
import logger from "@medusajs/cli/dist/reporter"
|
||||
import { json, NextFunction, RequestHandler, text, urlencoded } from "express"
|
||||
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
MiddlewareVerb,
|
||||
ParserConfigArgs,
|
||||
MiddlewareFunction,
|
||||
BodyParserConfigRoute,
|
||||
} from "../types"
|
||||
import type { RoutesFinder } from "../routes-finder"
|
||||
|
||||
/**
|
||||
* Parsers to use for parsing the HTTP request body
|
||||
*/
|
||||
const parsers = {
|
||||
json: memoize(function jsonParserMiddleware(options?: ParserConfigArgs) {
|
||||
return json({
|
||||
limit: options?.sizeLimit,
|
||||
verify: options?.preserveRawBody
|
||||
? (req: MedusaRequest, res: MedusaResponse, buf: Buffer) => {
|
||||
req.rawBody = buf
|
||||
}
|
||||
: undefined,
|
||||
})
|
||||
}),
|
||||
text: memoize(function textParser(options?: ParserConfigArgs) {
|
||||
return text({
|
||||
limit: options?.sizeLimit,
|
||||
})
|
||||
}),
|
||||
urlencoded: memoize(function urlencodedParserMiddleware(
|
||||
options?: ParserConfigArgs
|
||||
) {
|
||||
return urlencoded({
|
||||
limit: options?.sizeLimit,
|
||||
extended: true,
|
||||
})
|
||||
}),
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the bodyparser middlewares stack that creates custom bodyparsers
|
||||
* during an HTTP request based upon the defined config. The bodyparser
|
||||
* instances are cached for re-use.
|
||||
*/
|
||||
export function createBodyParserMiddlewaresStack(
|
||||
route: string,
|
||||
routesFinder: RoutesFinder<BodyParserConfigRoute>,
|
||||
tracer?: (
|
||||
handler: RequestHandler | MiddlewareFunction,
|
||||
route: { route: string; method?: string }
|
||||
) => RequestHandler | MiddlewareFunction
|
||||
) {
|
||||
return (["json", "text", "urlencoded"] as (keyof typeof parsers)[]).map(
|
||||
(parser) => {
|
||||
function bodyParser(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse,
|
||||
next: NextFunction
|
||||
) {
|
||||
const matchingRoute = routesFinder.find(
|
||||
req.path,
|
||||
req.method as MiddlewareVerb
|
||||
)
|
||||
const parserMiddleware = parsers[parser]
|
||||
|
||||
if (!matchingRoute) {
|
||||
return parserMiddleware()(req, res, next)
|
||||
}
|
||||
|
||||
if (matchingRoute.config === false) {
|
||||
logger.debug(
|
||||
`skipping ${parser} bodyparser middleware ${req.method} ${req.path}`
|
||||
)
|
||||
return next()
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`using custom ${parser} bodyparser config ${req.method} ${req.path}`
|
||||
)
|
||||
return parserMiddleware(matchingRoute.config)(req, res, next)
|
||||
}
|
||||
|
||||
Object.defineProperty(bodyParser, "name", {
|
||||
value: `${parser}BodyParser`,
|
||||
})
|
||||
|
||||
return (
|
||||
tracer ? tracer(bodyParser, { route }) : bodyParser
|
||||
) as RequestHandler
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
import { MedusaNextFunction, MedusaRequest } from "../types"
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../types"
|
||||
|
||||
export function clearFiltersByKey(keys: string[]) {
|
||||
return async (req: MedusaRequest, _, next: MedusaNextFunction) => {
|
||||
return async function clearFiltersByKeyMiddleware(
|
||||
req: MedusaRequest,
|
||||
_: MedusaResponse,
|
||||
next: MedusaNextFunction
|
||||
) {
|
||||
keys.forEach((key) => {
|
||||
delete req.filterableFields[key]
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
MedusaError,
|
||||
PUBLISHABLE_KEY_HEADER,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaResponse,
|
||||
MedusaStoreRequest,
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
|
||||
export async function ensurePublishableApiKeyMiddleware(
|
||||
req: MedusaStoreRequest,
|
||||
_res: MedusaResponse,
|
||||
_: MedusaResponse,
|
||||
next: MedusaNextFunction
|
||||
) {
|
||||
const publishableApiKey = req.get(PUBLISHABLE_KEY_HEADER)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NextFunction, Response } from "express"
|
||||
import { NextFunction, ErrorRequestHandler, Response } from "express"
|
||||
|
||||
import { ContainerRegistrationKeys, MedusaError } from "@medusajs/utils"
|
||||
import { formatException } from "./exception-formatter"
|
||||
@@ -13,12 +13,12 @@ const INVALID_REQUEST_ERROR = "invalid_request_error"
|
||||
const INVALID_STATE_ERROR = "invalid_state_error"
|
||||
|
||||
export function errorHandler() {
|
||||
return (
|
||||
return function coreErrorHandler(
|
||||
err: MedusaError,
|
||||
req: MedusaRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
_: NextFunction
|
||||
) {
|
||||
const logger = req.scope.resolve(ContainerRegistrationKeys.LOGGER)
|
||||
|
||||
err = formatException(err)
|
||||
@@ -76,7 +76,7 @@ export function errorHandler() {
|
||||
}
|
||||
|
||||
res.status(statusCode).json(errObj)
|
||||
}
|
||||
} as unknown as ErrorRequestHandler
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
||||
import pathToRegexp from "path-to-regexp"
|
||||
import type { MiddlewareVerb, RouteVerb } from "./types"
|
||||
|
||||
export class RoutesFinder<
|
||||
T extends
|
||||
| { matcher: string; methods: MiddlewareVerb | MiddlewareVerb[] }
|
||||
| { matcher: string; method: RouteVerb }
|
||||
> {
|
||||
/**
|
||||
* Cache of existing matches to avoid regex tests on every
|
||||
* single HTTP request
|
||||
*/
|
||||
#existingMatches: Map<
|
||||
string,
|
||||
| (T & {
|
||||
matchRegex: RegExp
|
||||
})
|
||||
| null
|
||||
> = new Map()
|
||||
|
||||
/**
|
||||
* Collection of registered routes
|
||||
*/
|
||||
#routes: (T & {
|
||||
matchRegex: RegExp
|
||||
})[] = []
|
||||
|
||||
constructor(routes?: T[]) {
|
||||
if (routes) {
|
||||
routes.forEach((route) => this.add(route))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register route for lookup
|
||||
*/
|
||||
add(route: T) {
|
||||
this.#routes.push({
|
||||
...route,
|
||||
matchRegex: pathToRegexp(route.matcher),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the matching route for a given HTTP method and URL
|
||||
*/
|
||||
find(url: string, method: MiddlewareVerb) {
|
||||
const key = `${method}:${url}`
|
||||
if (this.#existingMatches.has(key)) {
|
||||
return this.#existingMatches.get(key)
|
||||
}
|
||||
|
||||
const result =
|
||||
this.#routes.find((route) => {
|
||||
if ("methods" in route) {
|
||||
return route.methods.includes(method) && route.matchRegex.test(url)
|
||||
}
|
||||
return route.method === method && route.matchRegex.test(url)
|
||||
}) ?? null
|
||||
|
||||
this.#existingMatches.set(key, result)
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,7 @@
|
||||
import { join, parse, sep } from "path"
|
||||
import { dynamicImport, readDirRecursive } from "@medusajs/utils"
|
||||
import { logger } from "../logger"
|
||||
import {
|
||||
type RouteVerb,
|
||||
HTTP_METHODS,
|
||||
type ScannedRouteDescriptor,
|
||||
type FileSystemRouteDescriptor,
|
||||
} from "./types"
|
||||
import { type RouteVerb, HTTP_METHODS, type RouteDescriptor } from "./types"
|
||||
|
||||
/**
|
||||
* File name that is used to indicate that the file is a route file
|
||||
@@ -43,21 +38,6 @@ const ADMIN_ROUTE_MATCH = /(\/admin$|\/admin\/)/
|
||||
const STORE_ROUTE_MATCH = /(\/store$|\/store\/)/
|
||||
const AUTH_ROUTE_MATCH = /(\/auth$|\/auth\/)/
|
||||
|
||||
const log = ({
|
||||
activityId,
|
||||
message,
|
||||
}: {
|
||||
activityId?: string
|
||||
message: string
|
||||
}) => {
|
||||
if (activityId) {
|
||||
logger.progress(activityId, message)
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes to API to register routes manually or by scanning the filesystem from a
|
||||
* source directory.
|
||||
@@ -69,19 +49,7 @@ export class RoutesLoader {
|
||||
/**
|
||||
* Routes collected manually or by scanning directories
|
||||
*/
|
||||
#routes: Record<
|
||||
string,
|
||||
Record<string, ScannedRouteDescriptor | FileSystemRouteDescriptor>
|
||||
> = {}
|
||||
|
||||
/**
|
||||
* An eventual activity id for information tracking
|
||||
*/
|
||||
readonly #activityId?: string
|
||||
|
||||
constructor({ activityId }: { activityId?: string }) {
|
||||
this.#activityId = activityId
|
||||
}
|
||||
#routes: Record<string, Record<string, RouteDescriptor>> = {}
|
||||
|
||||
/**
|
||||
* Creates the route path from its relative file path.
|
||||
@@ -96,10 +64,9 @@ export class RoutesLoader {
|
||||
if (segment.startsWith("[")) {
|
||||
segment = segment.replace(PARAM_SEGMENT_MATCHER, (_, group) => {
|
||||
if (params[group]) {
|
||||
log({
|
||||
activityId: this.#activityId,
|
||||
message: `Duplicate parameters found in route ${relativePath} (${group})`,
|
||||
})
|
||||
logger.debug(
|
||||
`Duplicate parameters found in route ${relativePath} (${group})`
|
||||
)
|
||||
|
||||
throw new Error(
|
||||
`Duplicate parameters found in route ${relativePath} (${group}). Make sure that all parameters are unique.`
|
||||
@@ -122,7 +89,7 @@ export class RoutesLoader {
|
||||
async #getRoutesForFile(
|
||||
routePath: string,
|
||||
absolutePath: string
|
||||
): Promise<ScannedRouteDescriptor[]> {
|
||||
): Promise<RouteDescriptor[]> {
|
||||
const routeExports = await dynamicImport(absolutePath)
|
||||
|
||||
/**
|
||||
@@ -161,10 +128,9 @@ export class RoutesLoader {
|
||||
}
|
||||
|
||||
if (!HTTP_METHODS.includes(key as RouteVerb)) {
|
||||
log({
|
||||
activityId: this.#activityId,
|
||||
message: `Skipping handler ${key} in ${absolutePath}. Invalid HTTP method: ${key}.`,
|
||||
})
|
||||
logger.debug(
|
||||
`Skipping handler ${key} in ${absolutePath}. Invalid HTTP method: ${key}.`
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -172,14 +138,15 @@ export class RoutesLoader {
|
||||
})
|
||||
.map((key) => {
|
||||
return {
|
||||
route: routePath,
|
||||
isRoute: true,
|
||||
matcher: routePath,
|
||||
method: key as RouteVerb,
|
||||
handler: routeExports[key],
|
||||
optedOutOfAuth: !shouldAuthenticate,
|
||||
shouldAppendAdminCors: shouldApplyCors && routeType === "admin",
|
||||
shouldAppendAuthCors: shouldApplyCors && routeType === "auth",
|
||||
shouldAppendStoreCors: shouldApplyCors && routeType === "store",
|
||||
} satisfies ScannedRouteDescriptor
|
||||
} satisfies RouteDescriptor
|
||||
})
|
||||
}
|
||||
|
||||
@@ -232,18 +199,16 @@ export class RoutesLoader {
|
||||
/**
|
||||
* Register a route
|
||||
*/
|
||||
registerRoute(route: ScannedRouteDescriptor | FileSystemRouteDescriptor) {
|
||||
this.#routes[route.route] = this.#routes[route.route] ?? {}
|
||||
const trackedRoute = this.#routes[route.route]
|
||||
registerRoute(route: RouteDescriptor) {
|
||||
this.#routes[route.matcher] = this.#routes[route.matcher] ?? {}
|
||||
const trackedRoute = this.#routes[route.matcher]
|
||||
trackedRoute[route.method] = route
|
||||
}
|
||||
|
||||
/**
|
||||
* Register one or more routes
|
||||
*/
|
||||
registerRoutes(
|
||||
routes: (ScannedRouteDescriptor | FileSystemRouteDescriptor)[]
|
||||
) {
|
||||
registerRoutes(routes: RouteDescriptor[]) {
|
||||
routes.forEach((route) => this.registerRoute(route))
|
||||
}
|
||||
|
||||
@@ -252,14 +217,16 @@ export class RoutesLoader {
|
||||
* manually.
|
||||
*/
|
||||
getRoutes() {
|
||||
return Object.keys(this.#routes).reduce<
|
||||
(ScannedRouteDescriptor | FileSystemRouteDescriptor)[]
|
||||
>((result, routePattern) => {
|
||||
const methodsRoutes = this.#routes[routePattern]
|
||||
Object.keys(methodsRoutes).forEach((method) => {
|
||||
result.push(methodsRoutes[method])
|
||||
})
|
||||
return result
|
||||
}, [])
|
||||
return Object.keys(this.#routes).reduce<RouteDescriptor[]>(
|
||||
(result, routePattern) => {
|
||||
const methodsRoutes = this.#routes[routePattern]
|
||||
Object.keys(methodsRoutes).forEach((method) => {
|
||||
const route = methodsRoutes[method]
|
||||
result.push(route)
|
||||
})
|
||||
return result
|
||||
},
|
||||
[]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { MiddlewareVerb } from "./types"
|
||||
import { MiddlewareVerb, RouteVerb } from "./types"
|
||||
|
||||
/**
|
||||
* Route represents both the middleware/routes defined via the
|
||||
* "defineMiddlewares" method and the routes scanned from
|
||||
* the filesystem. The later one's must be marked with "isAppRoute = true".
|
||||
* the filesystem.
|
||||
*/
|
||||
type Route = {
|
||||
/**
|
||||
@@ -17,10 +17,9 @@ type Route = {
|
||||
handler?: any
|
||||
|
||||
/**
|
||||
* Must be true when the route is discovered via the fileystem
|
||||
* scanning.
|
||||
* The HTTP method specified as a single value
|
||||
*/
|
||||
isAppRoute?: boolean
|
||||
method?: RouteVerb
|
||||
|
||||
/**
|
||||
* The HTTP methods this route is supposed to handle.
|
||||
@@ -48,39 +47,39 @@ type Route = {
|
||||
* - static
|
||||
* - params
|
||||
*/
|
||||
type RoutesBranch = {
|
||||
type RoutesBranch<T extends Route> = {
|
||||
global: {
|
||||
routes: Route[]
|
||||
routes: T[]
|
||||
children?: {
|
||||
[segment: string]: RoutesBranch
|
||||
[segment: string]: RoutesBranch<T>
|
||||
}
|
||||
}
|
||||
|
||||
regex: {
|
||||
routes: Route[]
|
||||
routes: T[]
|
||||
children?: {
|
||||
[segment: string]: RoutesBranch
|
||||
[segment: string]: RoutesBranch<T>
|
||||
}
|
||||
}
|
||||
|
||||
wildcard: {
|
||||
routes: Route[]
|
||||
routes: T[]
|
||||
children?: {
|
||||
[segment: string]: RoutesBranch
|
||||
[segment: string]: RoutesBranch<T>
|
||||
}
|
||||
}
|
||||
|
||||
params: {
|
||||
routes: Route[]
|
||||
routes: T[]
|
||||
children?: {
|
||||
[segment: string]: RoutesBranch
|
||||
[segment: string]: RoutesBranch<T>
|
||||
}
|
||||
}
|
||||
|
||||
static: {
|
||||
routes: Route[]
|
||||
routes: T[]
|
||||
children?: {
|
||||
[segment: string]: RoutesBranch
|
||||
[segment: string]: RoutesBranch<T>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,29 +90,42 @@ type RoutesBranch = {
|
||||
* like structure and then sort them back to a flat array based upon the
|
||||
* priorities of different types of nodes.
|
||||
*/
|
||||
export class RoutesSorter {
|
||||
export class RoutesSorter<T extends Route> {
|
||||
/**
|
||||
* The order in which the routes will be sorted. This
|
||||
* can be overridden at the time of call the sort
|
||||
* method.
|
||||
*/
|
||||
#orderBy: [
|
||||
keyof RoutesBranch<T>,
|
||||
keyof RoutesBranch<T>,
|
||||
keyof RoutesBranch<T>,
|
||||
keyof RoutesBranch<T>,
|
||||
keyof RoutesBranch<T>
|
||||
] = ["global", "wildcard", "regex", "static", "params"]
|
||||
|
||||
/**
|
||||
* Input routes
|
||||
*/
|
||||
#routesToProcess: Route[]
|
||||
#routesToProcess: T[]
|
||||
|
||||
/**
|
||||
* Intermediate tree representation for sorting routes
|
||||
*/
|
||||
#routesTree: {
|
||||
[segment: string]: RoutesBranch
|
||||
[segment: string]: RoutesBranch<T>
|
||||
} = {
|
||||
root: this.#createBranch(),
|
||||
}
|
||||
|
||||
constructor(routes: Route[]) {
|
||||
constructor(routes: T[]) {
|
||||
this.#routesToProcess = routes
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty branch with known nodes
|
||||
*/
|
||||
#createBranch(): RoutesBranch {
|
||||
#createBranch(): RoutesBranch<T> {
|
||||
return {
|
||||
global: {
|
||||
routes: [],
|
||||
@@ -162,14 +174,14 @@ export class RoutesSorter {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
#processRoute(route: Route) {
|
||||
#processRoute(route: T) {
|
||||
const segments = route.matcher.split("/").filter((s) => s.length)
|
||||
let parent = this.#routesTree["root"]
|
||||
|
||||
segments.forEach((segment, index) => {
|
||||
let bucket: keyof RoutesBranch = "static"
|
||||
let bucket: keyof RoutesBranch<T> = "static"
|
||||
|
||||
if (!route.methods) {
|
||||
if (!route.methods && !route.method) {
|
||||
bucket = "global"
|
||||
} else if (segment.startsWith("*")) {
|
||||
bucket = "wildcard"
|
||||
@@ -194,40 +206,51 @@ export class RoutesSorter {
|
||||
/**
|
||||
* Returns an array of sorted routes for a given branch.
|
||||
*/
|
||||
#sortBranch(routeBranch: { [segment: string]: RoutesBranch }) {
|
||||
#sortBranch(
|
||||
routeBranch: { [segment: string]: RoutesBranch<T> },
|
||||
orderBy: [
|
||||
keyof RoutesBranch<T>,
|
||||
keyof RoutesBranch<T>,
|
||||
keyof RoutesBranch<T>,
|
||||
keyof RoutesBranch<T>,
|
||||
keyof RoutesBranch<T>
|
||||
]
|
||||
) {
|
||||
const branchRoutes = Object.keys(routeBranch).reduce<{
|
||||
global: Route[]
|
||||
wildcard: Route[]
|
||||
regex: Route[]
|
||||
params: Route[]
|
||||
static: Route[]
|
||||
global: T[]
|
||||
wildcard: T[]
|
||||
regex: T[]
|
||||
params: T[]
|
||||
static: T[]
|
||||
}>(
|
||||
(result, branchKey) => {
|
||||
const node = routeBranch[branchKey]
|
||||
|
||||
result.global.push(...node.global.routes)
|
||||
if (node.global.children) {
|
||||
result.global.push(...this.#sortBranch(node.global.children))
|
||||
result.global.push(...this.#sortBranch(node.global.children, orderBy))
|
||||
}
|
||||
|
||||
result.wildcard.push(...node.wildcard.routes)
|
||||
if (node.wildcard.children) {
|
||||
result.wildcard.push(...this.#sortBranch(node.wildcard.children))
|
||||
result.wildcard.push(
|
||||
...this.#sortBranch(node.wildcard.children, orderBy)
|
||||
)
|
||||
}
|
||||
|
||||
result.regex.push(...node.regex.routes)
|
||||
if (node.regex.children) {
|
||||
result.regex.push(...this.#sortBranch(node.regex.children))
|
||||
result.regex.push(...this.#sortBranch(node.regex.children, orderBy))
|
||||
}
|
||||
|
||||
result.static.push(...node.static.routes)
|
||||
if (node.static.children) {
|
||||
result.static.push(...this.#sortBranch(node.static.children))
|
||||
result.static.push(...this.#sortBranch(node.static.children, orderBy))
|
||||
}
|
||||
|
||||
result.params.push(...node.params.routes)
|
||||
if (node.params.children) {
|
||||
result.params.push(...this.#sortBranch(node.params.children))
|
||||
result.params.push(...this.#sortBranch(node.params.children, orderBy))
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -244,19 +267,33 @@ export class RoutesSorter {
|
||||
/**
|
||||
* Concatenating routes as per their priority.
|
||||
*/
|
||||
const routes: Route[] = branchRoutes.global
|
||||
.concat(branchRoutes.wildcard)
|
||||
.concat(branchRoutes.regex)
|
||||
.concat(branchRoutes.static)
|
||||
.concat(branchRoutes.params)
|
||||
return routes
|
||||
return orderBy.reduce<T[]>((result, branch) => {
|
||||
result = result.concat(branchRoutes[branch])
|
||||
return result
|
||||
}, [])
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the input routes
|
||||
* Returns the intermediate representation of routes as a tree.
|
||||
*/
|
||||
sort() {
|
||||
getTree() {
|
||||
return this.#routesTree
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the input routes. You can optionally specify a custom
|
||||
* orderBy array. Defaults to: ["global", "wildcard", "regex", "static", "params"]
|
||||
*/
|
||||
sort(
|
||||
orderBy?: [
|
||||
keyof RoutesBranch<T>,
|
||||
keyof RoutesBranch<T>,
|
||||
keyof RoutesBranch<T>,
|
||||
keyof RoutesBranch<T>,
|
||||
keyof RoutesBranch<T>
|
||||
]
|
||||
) {
|
||||
this.#routesToProcess.map((route) => this.#processRoute(route))
|
||||
return this.#sortBranch(this.#routesTree)
|
||||
return this.#sortBranch(this.#routesTree, orderBy ?? this.#orderBy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,20 +34,6 @@ export type AsyncRouteHandler = (
|
||||
|
||||
export type RouteHandler = SyncRouteHandler | AsyncRouteHandler
|
||||
|
||||
export type RouteImplementation = {
|
||||
method?: RouteVerb
|
||||
handler: RouteHandler
|
||||
}
|
||||
|
||||
export type RouteConfig = {
|
||||
optedOutOfAuth?: boolean
|
||||
routeType?: "admin" | "store" | "auth"
|
||||
shouldAppendAdminCors?: boolean
|
||||
shouldAppendStoreCors?: boolean
|
||||
shouldAppendAuthCors?: boolean
|
||||
routes?: RouteImplementation[]
|
||||
}
|
||||
|
||||
export type MiddlewareFunction =
|
||||
| MedusaRequestHandler
|
||||
| ((...args: any[]) => any)
|
||||
@@ -67,7 +53,11 @@ export type ParserConfigArgs = {
|
||||
export type ParserConfig = false | ParserConfigArgs
|
||||
|
||||
export type MiddlewareRoute = {
|
||||
/**
|
||||
* @deprecated. Instead use {@link MiddlewareRoute.methods}
|
||||
*/
|
||||
method?: MiddlewareVerb | MiddlewareVerb[]
|
||||
methods?: MiddlewareVerb[]
|
||||
matcher: string | RegExp
|
||||
bodyParser?: ParserConfig
|
||||
middlewares?: MiddlewareFunction[]
|
||||
@@ -78,49 +68,38 @@ export type MiddlewaresConfig = {
|
||||
routes?: MiddlewareRoute[]
|
||||
}
|
||||
|
||||
export type RouteDescriptor = {
|
||||
absolutePath: string
|
||||
relativePath: string
|
||||
route: string
|
||||
priority: number
|
||||
config?: RouteConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* Route descriptor refers represents a route either scanned
|
||||
* from the filesystem or registered manually. It does not
|
||||
* represent a middleware
|
||||
*/
|
||||
export type ScannedRouteDescriptor = {
|
||||
route: string
|
||||
export type RouteDescriptor = {
|
||||
matcher: string
|
||||
method: RouteVerb
|
||||
handler: RouteHandler
|
||||
optedOutOfAuth: boolean
|
||||
isRoute: true
|
||||
routeType?: "admin" | "store" | "auth"
|
||||
absolutePath?: string
|
||||
relativePath?: string
|
||||
shouldAppendAdminCors: boolean
|
||||
shouldAppendStoreCors: boolean
|
||||
shouldAppendAuthCors: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* FileSystem route description represents a route scanned from
|
||||
* the filesystem
|
||||
* Represents a middleware
|
||||
*/
|
||||
export type FileSystemRouteDescriptor = ScannedRouteDescriptor & {
|
||||
absolutePath: string
|
||||
relativePath: string
|
||||
}
|
||||
|
||||
export type ScannedMiddlewareDescriptor = {
|
||||
export type MiddlewareDescriptor = {
|
||||
matcher: string
|
||||
method?: MiddlewareVerb | MiddlewareVerb[]
|
||||
methods?: MiddlewareVerb | MiddlewareVerb[]
|
||||
handler: MiddlewareFunction
|
||||
}
|
||||
|
||||
export type BodyParserConfigRoute = {
|
||||
matcher: string
|
||||
method?: MiddlewareVerb | MiddlewareVerb[]
|
||||
config?: ParserConfig
|
||||
methods: MiddlewareVerb | MiddlewareVerb[]
|
||||
config: ParserConfig
|
||||
}
|
||||
|
||||
export type GlobalMiddlewareDescriptor = {
|
||||
|
||||
@@ -16,7 +16,11 @@ import zod, { ZodRawShape } from "zod"
|
||||
*/
|
||||
export function defineMiddlewares<
|
||||
Route extends {
|
||||
/**
|
||||
* @deprecated. Instead use {@link MiddlewareRoute.methods}
|
||||
*/
|
||||
method?: MiddlewareVerb | MiddlewareVerb[]
|
||||
methods?: MiddlewareVerb[]
|
||||
matcher: string | RegExp
|
||||
bodyParser?: ParserConfig
|
||||
additionalDataValidator?: ZodRawShape
|
||||
@@ -38,7 +42,8 @@ export function defineMiddlewares<
|
||||
return {
|
||||
errorHandler,
|
||||
routes: routes.map((route) => {
|
||||
const { middlewares, additionalDataValidator, ...rest } = route
|
||||
let { middlewares, method, methods, additionalDataValidator, ...rest } =
|
||||
route
|
||||
const customMiddleware: MedusaRequestHandler[] = []
|
||||
|
||||
/**
|
||||
@@ -54,8 +59,13 @@ export function defineMiddlewares<
|
||||
})
|
||||
}
|
||||
|
||||
if (!methods) {
|
||||
methods = Array.isArray(method) ? method : method ? [method] : method
|
||||
}
|
||||
|
||||
return {
|
||||
...rest,
|
||||
methods,
|
||||
middlewares: customMiddleware.concat(middlewares || []),
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -3,7 +3,11 @@ import {
|
||||
ContainerRegistrationKeys,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import { MedusaNextFunction, MedusaRequest } from "../types"
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../types"
|
||||
|
||||
export function maybeApplyLinkFilter({
|
||||
entryPoint,
|
||||
@@ -13,7 +17,7 @@ export function maybeApplyLinkFilter({
|
||||
}) {
|
||||
return async function linkFilter(
|
||||
req: MedusaRequest,
|
||||
_,
|
||||
_: MedusaResponse,
|
||||
next: MedusaNextFunction
|
||||
) {
|
||||
const filterableFields = req.filterableFields
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
MiddlewareFunction,
|
||||
RouteHandler,
|
||||
} from "../types"
|
||||
|
||||
export const wrapHandler = <T extends RouteHandler | MiddlewareFunction>(
|
||||
fn: T
|
||||
) => {
|
||||
async function wrappedHandler(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse,
|
||||
next: MedusaNextFunction
|
||||
) {
|
||||
const req_ = req as MedusaRequest & { errors?: Error[] }
|
||||
if (req_?.errors?.length) {
|
||||
return res.status(400).json({
|
||||
errors: req_.errors,
|
||||
message:
|
||||
"Provided request body contains errors. Please check the data and retry the request",
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
return await fn(req, res, next)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
|
||||
if (fn.name) {
|
||||
Object.defineProperty(wrappedHandler, "name", { value: fn.name })
|
||||
}
|
||||
return wrappedHandler as T
|
||||
}
|
||||
Reference in New Issue
Block a user