feat: Replace existing router with the new implementation (#11646)

This commit is contained in:
Harminder Virk
2025-03-04 17:22:20 +05:30
committed by GitHub
parent c2e24b6cfd
commit aabbbb7292
30 changed files with 992 additions and 1320 deletions

View File

@@ -0,0 +1,6 @@
---
"@medusajs/medusa": patch
"@medusajs/framework": patch
---
feat: Replace existing router with the new implementation

View File

@@ -93,6 +93,7 @@
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lodash": "4.17.21", "lodash": "4.17.21",
"morgan": "^1.9.1", "morgan": "^1.9.1",
"path-to-regexp": "^0.1.10",
"tsconfig-paths": "^4.2.0", "tsconfig-paths": "^4.2.0",
"zod": "3.22.4" "zod": "3.22.4"
}, },

View File

@@ -16,7 +16,7 @@ import { container } from "../../../container"
import { featureFlagsLoader } from "../../../feature-flags" import { featureFlagsLoader } from "../../../feature-flags"
import { logger } from "../../../logger" import { logger } from "../../../logger"
import { MedusaRequest } from "../../types" import { MedusaRequest } from "../../types"
import { RoutesLoader } from "../../router" import { ApiLoader } from "../../router"
function asArray(resolvers) { function asArray(resolvers) {
return { return {
@@ -94,7 +94,7 @@ export const createServer = async (rootDir) => {
next() next()
}) })
await new RoutesLoader({ await new ApiLoader({
app, app,
sourceDir: rootDir, sourceDir: rootDir,
}).load() }).load()

View File

@@ -6,7 +6,7 @@ import {
storeGlobalMiddlewareMock, storeGlobalMiddlewareMock,
} from "../__fixtures__/mocks" } from "../__fixtures__/mocks"
import { createServer } from "../__fixtures__/server" import { createServer } from "../__fixtures__/server"
import { MedusaNextFunction, RoutesLoader } from "../index" import { MedusaNextFunction, ApiLoader } from "../index"
jest.setTimeout(30000) jest.setTimeout(30000)
@@ -237,7 +237,7 @@ describe("RoutesLoader", function () {
__dirname, __dirname,
"../__fixtures__/routers-duplicate-parameter" "../__fixtures__/routers-duplicate-parameter"
) )
const err = await new RoutesLoader({ const err = await new ApiLoader({
app, app,
sourceDir: rootDir, sourceDir: rootDir,
}) })
@@ -246,7 +246,7 @@ describe("RoutesLoader", function () {
expect(err).toBeDefined() expect(err).toBeDefined()
expect(err.message).toBe( 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."
) )
}) })
}) })

View File

@@ -4,7 +4,7 @@ import { MiddlewareFileLoader } from "../middleware-file-loader"
describe("Middleware file loader", () => { describe("Middleware file loader", () => {
it("should load routes from the filesystem", async () => { it("should load routes from the filesystem", async () => {
const BASE_DIR = resolve(__dirname, "../__fixtures__/routers-middleware") const BASE_DIR = resolve(__dirname, "../__fixtures__/routers-middleware")
const loader = new MiddlewareFileLoader({}) const loader = new MiddlewareFileLoader()
await loader.scanDir(BASE_DIR) await loader.scanDir(BASE_DIR)
expect(loader.getBodyParserConfigRoutes()).toMatchInlineSnapshot(` expect(loader.getBodyParserConfigRoutes()).toMatchInlineSnapshot(`
@@ -14,12 +14,22 @@ describe("Middleware file loader", () => {
"preserveRawBody": true, "preserveRawBody": true,
}, },
"matcher": "/webhooks", "matcher": "/webhooks",
"method": undefined, "methods": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE",
"OPTIONS",
"HEAD",
],
}, },
{ {
"config": false, "config": false,
"matcher": "/webhooks/*", "matcher": "/webhooks/*",
"method": "POST", "methods": [
"POST",
],
}, },
] ]
`) `)
@@ -28,22 +38,26 @@ describe("Middleware file loader", () => {
{ {
"handler": [Function], "handler": [Function],
"matcher": "/customers", "matcher": "/customers",
"method": undefined, "methods": undefined,
}, },
{ {
"handler": [Function], "handler": [Function],
"matcher": "/customers", "matcher": "/customers",
"method": "POST", "methods": [
"POST",
],
}, },
{ {
"handler": [Function], "handler": [Function],
"matcher": "/store/*", "matcher": "/store/*",
"method": undefined, "methods": undefined,
}, },
{ {
"handler": [Function], "handler": [Function],
"matcher": "/webhooks/*", "matcher": "/webhooks/*",
"method": "POST", "methods": [
"POST",
],
}, },
] ]
`) `)

View File

@@ -4,7 +4,7 @@ import { RoutesLoader } from "../routes-loader"
describe("Routes loader", () => { describe("Routes loader", () => {
it("should load routes from the filesystem", async () => { it("should load routes from the filesystem", async () => {
const BASE_DIR = resolve(__dirname, "../__fixtures__/routers") const BASE_DIR = resolve(__dirname, "../__fixtures__/routers")
const loader = new RoutesLoader({}) const loader = new RoutesLoader()
await loader.scanDir(BASE_DIR) await loader.scanDir(BASE_DIR)
expect(loader.getRoutes()).toMatchInlineSnapshot(` expect(loader.getRoutes()).toMatchInlineSnapshot(`
@@ -12,10 +12,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/orders/[id]/route.ts", "absolutePath": "${BASE_DIR}/admin/orders/[id]/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/orders/:id",
"method": "GET", "method": "GET",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/orders/[id]/route.ts", "relativePath": "/admin/orders/[id]/route.ts",
"route": "/admin/orders/:id",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -23,10 +24,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/orders/[id]/route.ts", "absolutePath": "${BASE_DIR}/admin/orders/[id]/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/orders/:id",
"method": "POST", "method": "POST",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/orders/[id]/route.ts", "relativePath": "/admin/orders/[id]/route.ts",
"route": "/admin/orders/:id",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -34,10 +36,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/orders/route.ts", "absolutePath": "${BASE_DIR}/admin/orders/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/orders",
"method": "GET", "method": "GET",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/orders/route.ts", "relativePath": "/admin/orders/route.ts",
"route": "/admin/orders",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -45,10 +48,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/products/[id]/route.ts", "absolutePath": "${BASE_DIR}/admin/products/[id]/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products/:id",
"method": "GET", "method": "GET",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/products/[id]/route.ts", "relativePath": "/admin/products/[id]/route.ts",
"route": "/admin/products/:id",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -56,10 +60,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/products/route.ts", "absolutePath": "${BASE_DIR}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "DELETE", "method": "DELETE",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -67,10 +72,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/products/route.ts", "absolutePath": "${BASE_DIR}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "GET", "method": "GET",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -78,10 +84,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/products/route.ts", "absolutePath": "${BASE_DIR}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "HEAD", "method": "HEAD",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -89,10 +96,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/products/route.ts", "absolutePath": "${BASE_DIR}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "OPTIONS", "method": "OPTIONS",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -100,10 +108,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/products/route.ts", "absolutePath": "${BASE_DIR}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "PATCH", "method": "PATCH",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -111,10 +120,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/products/route.ts", "absolutePath": "${BASE_DIR}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "POST", "method": "POST",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -122,10 +132,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/products/route.ts", "absolutePath": "${BASE_DIR}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "PUT", "method": "PUT",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -133,10 +144,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/route.ts", "absolutePath": "${BASE_DIR}/admin/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin",
"method": "GET", "method": "GET",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/route.ts", "relativePath": "/admin/route.ts",
"route": "/admin",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -144,10 +156,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/route.ts", "absolutePath": "${BASE_DIR}/admin/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin",
"method": "POST", "method": "POST",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/route.ts", "relativePath": "/admin/route.ts",
"route": "/admin",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -155,10 +168,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/customers/[customer_id]/orders/[order_id]/route.ts", "absolutePath": "${BASE_DIR}/customers/[customer_id]/orders/[order_id]/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/customers/:customer_id/orders/:order_id",
"method": "GET", "method": "GET",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/customers/[customer_id]/orders/[order_id]/route.ts", "relativePath": "/customers/[customer_id]/orders/[order_id]/route.ts",
"route": "/customers/:customer_id/orders/:order_id",
"shouldAppendAdminCors": false, "shouldAppendAdminCors": false,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -166,10 +180,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/customers/route.ts", "absolutePath": "${BASE_DIR}/customers/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/customers",
"method": "GET", "method": "GET",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/customers/route.ts", "relativePath": "/customers/route.ts",
"route": "/customers",
"shouldAppendAdminCors": false, "shouldAppendAdminCors": false,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -184,7 +199,7 @@ describe("Routes loader", () => {
__dirname, __dirname,
"../__fixtures__/routers-with-duplicates" "../__fixtures__/routers-with-duplicates"
) )
const loader = new RoutesLoader({}) const loader = new RoutesLoader()
await loader.scanDir(BASE_DIR) await loader.scanDir(BASE_DIR)
await loader.scanDir(BASE_DIR_2) await loader.scanDir(BASE_DIR_2)
@@ -193,10 +208,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/orders/[id]/route.ts", "absolutePath": "${BASE_DIR}/admin/orders/[id]/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/orders/:id",
"method": "GET", "method": "GET",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/orders/[id]/route.ts", "relativePath": "/admin/orders/[id]/route.ts",
"route": "/admin/orders/:id",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -204,10 +220,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/orders/[id]/route.ts", "absolutePath": "${BASE_DIR}/admin/orders/[id]/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/orders/:id",
"method": "POST", "method": "POST",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/orders/[id]/route.ts", "relativePath": "/admin/orders/[id]/route.ts",
"route": "/admin/orders/:id",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -215,10 +232,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/orders/route.ts", "absolutePath": "${BASE_DIR}/admin/orders/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/orders",
"method": "GET", "method": "GET",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/orders/route.ts", "relativePath": "/admin/orders/route.ts",
"route": "/admin/orders",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -226,10 +244,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR_2}/admin/products/[id]/route.ts", "absolutePath": "${BASE_DIR_2}/admin/products/[id]/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products/:id",
"method": "GET", "method": "GET",
"optedOutOfAuth": true, "optedOutOfAuth": true,
"relativePath": "/admin/products/[id]/route.ts", "relativePath": "/admin/products/[id]/route.ts",
"route": "/admin/products/:id",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -237,10 +256,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/products/route.ts", "absolutePath": "${BASE_DIR}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "DELETE", "method": "DELETE",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -248,10 +268,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR_2}/admin/products/route.ts", "absolutePath": "${BASE_DIR_2}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "GET", "method": "GET",
"optedOutOfAuth": true, "optedOutOfAuth": true,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -259,10 +280,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/products/route.ts", "absolutePath": "${BASE_DIR}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "HEAD", "method": "HEAD",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -270,10 +292,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/products/route.ts", "absolutePath": "${BASE_DIR}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "OPTIONS", "method": "OPTIONS",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -281,10 +304,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/products/route.ts", "absolutePath": "${BASE_DIR}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "PATCH", "method": "PATCH",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -292,10 +316,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR_2}/admin/products/route.ts", "absolutePath": "${BASE_DIR_2}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "POST", "method": "POST",
"optedOutOfAuth": true, "optedOutOfAuth": true,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -303,10 +328,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/products/route.ts", "absolutePath": "${BASE_DIR}/admin/products/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin/products",
"method": "PUT", "method": "PUT",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/products/route.ts", "relativePath": "/admin/products/route.ts",
"route": "/admin/products",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -314,10 +340,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/route.ts", "absolutePath": "${BASE_DIR}/admin/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin",
"method": "GET", "method": "GET",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/route.ts", "relativePath": "/admin/route.ts",
"route": "/admin",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -325,10 +352,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/admin/route.ts", "absolutePath": "${BASE_DIR}/admin/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/admin",
"method": "POST", "method": "POST",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/admin/route.ts", "relativePath": "/admin/route.ts",
"route": "/admin",
"shouldAppendAdminCors": true, "shouldAppendAdminCors": true,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -336,10 +364,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/customers/[customer_id]/orders/[order_id]/route.ts", "absolutePath": "${BASE_DIR}/customers/[customer_id]/orders/[order_id]/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/customers/:customer_id/orders/:order_id",
"method": "GET", "method": "GET",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/customers/[customer_id]/orders/[order_id]/route.ts", "relativePath": "/customers/[customer_id]/orders/[order_id]/route.ts",
"route": "/customers/:customer_id/orders/:order_id",
"shouldAppendAdminCors": false, "shouldAppendAdminCors": false,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -347,10 +376,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR}/customers/route.ts", "absolutePath": "${BASE_DIR}/customers/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/customers",
"method": "GET", "method": "GET",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/customers/route.ts", "relativePath": "/customers/route.ts",
"route": "/customers",
"shouldAppendAdminCors": false, "shouldAppendAdminCors": false,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": false, "shouldAppendStoreCors": false,
@@ -358,10 +388,11 @@ describe("Routes loader", () => {
{ {
"absolutePath": "${BASE_DIR_2}/store/[customer_id]/orders/[order_id]/route.ts", "absolutePath": "${BASE_DIR_2}/store/[customer_id]/orders/[order_id]/route.ts",
"handler": [Function], "handler": [Function],
"isRoute": true,
"matcher": "/store/:customer_id/orders/:order_id",
"method": "GET", "method": "GET",
"optedOutOfAuth": false, "optedOutOfAuth": false,
"relativePath": "/store/[customer_id]/orders/[order_id]/route.ts", "relativePath": "/store/[customer_id]/orders/[order_id]/route.ts",
"route": "/store/:customer_id/orders/:order_id",
"shouldAppendAdminCors": false, "shouldAppendAdminCors": false,
"shouldAppendAuthCors": false, "shouldAppendAuthCors": false,
"shouldAppendStoreCors": true, "shouldAppendStoreCors": true,
@@ -376,7 +407,7 @@ describe("Routes loader", () => {
"../__fixtures__/routers-duplicate-parameter" "../__fixtures__/routers-duplicate-parameter"
) )
const loader = new RoutesLoader({}) const loader = new RoutesLoader()
await expect(() => loader.scanDir(BASE_DIR)).rejects.toThrow( 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." "Duplicate parameters found in route /admin/customers/[id]/orders/[id]/route.ts (id). Make sure that all parameters are unique."
) )

View File

@@ -19,6 +19,14 @@ describe("Routes sorter", () => {
isAppRoute: true, isAppRoute: true,
handler: () => {}, handler: () => {},
}, },
{
matcher: "/admin",
handler: () => {},
},
{
matcher: "/store",
handler: () => {},
},
{ {
matcher: "/admin/products/export", matcher: "/admin/products/export",
methods: ["GET"], methods: ["GET"],
@@ -32,12 +40,20 @@ describe("Routes sorter", () => {
}, },
{ {
matcher: "/admin/products/:id", matcher: "/admin/products/:id",
isAppRoute: true,
methods: ["GET"], methods: ["GET"],
handler: () => {}, handler: () => {},
}, },
{
matcher: "/admin/products/:id",
isAppRoute: true,
methods: ["POST"],
handler: () => {},
},
{ {
matcher: "/admin/products/batch", matcher: "/admin/products/batch",
methods: ["GET"], isAppRoute: true,
methods: ["POST"],
handler: () => {}, handler: () => {},
}, },
{ {
@@ -57,6 +73,7 @@ describe("Routes sorter", () => {
}, },
{ {
matcher: "/admin/:id/export", matcher: "/admin/:id/export",
isAppRoute: true,
methods: ["GET"], methods: ["GET"],
handler: () => {}, handler: () => {},
}, },
@@ -69,6 +86,14 @@ describe("Routes sorter", () => {
expect(sorter.sort()).toMatchInlineSnapshot(` expect(sorter.sort()).toMatchInlineSnapshot(`
[ [
{
"handler": [Function],
"matcher": "/admin",
},
{
"handler": [Function],
"matcher": "/store",
},
{ {
"handler": [Function], "handler": [Function],
"matcher": "/v1", "matcher": "/v1",
@@ -136,13 +161,15 @@ describe("Routes sorter", () => {
}, },
{ {
"handler": [Function], "handler": [Function],
"isAppRoute": true,
"matcher": "/admin/products/batch", "matcher": "/admin/products/batch",
"methods": [ "methods": [
"GET", "POST",
], ],
}, },
{ {
"handler": [Function], "handler": [Function],
"isAppRoute": true,
"matcher": "/admin/products/:id", "matcher": "/admin/products/:id",
"methods": [ "methods": [
"GET", "GET",
@@ -150,6 +177,15 @@ describe("Routes sorter", () => {
}, },
{ {
"handler": [Function], "handler": [Function],
"isAppRoute": true,
"matcher": "/admin/products/:id",
"methods": [
"POST",
],
},
{
"handler": [Function],
"isAppRoute": true,
"matcher": "/admin/:id/export", "matcher": "/admin/:id/export",
"methods": [ "methods": [
"GET", "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",
],
},
]
`)
})
}) })

View File

@@ -2,10 +2,12 @@ import { join } from "path"
import { dynamicImport, FileSystem } from "@medusajs/utils" import { dynamicImport, FileSystem } from "@medusajs/utils"
import { logger } from "../logger" import { logger } from "../logger"
import type { import {
MiddlewaresConfig, type MiddlewaresConfig,
BodyParserConfigRoute, type BodyParserConfigRoute,
ScannedMiddlewareDescriptor, type MiddlewareDescriptor,
type MedusaErrorHandlerFunction,
HTTP_METHODS,
} from "./types" } from "./types"
/** /**
@@ -13,21 +15,6 @@ import type {
*/ */
const MIDDLEWARE_FILE_NAME = "middlewares" 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 * 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 * the configuration for certain global middlewares and core routes validators. Also, it may
@@ -35,19 +22,19 @@ const log = ({
*/ */
export class MiddlewareFileLoader { export class MiddlewareFileLoader {
/** /**
* Middleware collected manually or by scanning directories * Global error handler exported from the middleware file loader
*/ */
#middleware: ScannedMiddlewareDescriptor[] = [] #errorHandler?: MedusaErrorHandlerFunction
#bodyParserConfigRoutes: BodyParserConfigRoute[] = []
/** /**
* 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 * Processes the middleware file and returns the middleware and the
@@ -58,25 +45,23 @@ export class MiddlewareFileLoader {
const middlewareConfig = middlewareExports.default const middlewareConfig = middlewareExports.default
if (!middlewareConfig) { if (!middlewareConfig) {
log({ logger.warn(
activityId: this.#activityId, `No middleware configuration found in ${absolutePath}. Skipping middleware configuration.`
message: `No middleware configuration found in ${absolutePath}. Skipping middleware configuration.`, )
})
return return
} }
const routes = middlewareConfig.routes as MiddlewaresConfig["routes"] const routes = middlewareConfig.routes as MiddlewaresConfig["routes"]
if (!routes || !Array.isArray(routes)) { if (!routes || !Array.isArray(routes)) {
log({ logger.warn(
activityId: this.#activityId, `Invalid default export found in ${absolutePath}. Make sure to use "defineMiddlewares" function and export its output.`
message: `Invalid default export found in ${absolutePath}. Make sure to use "defineMiddlewares" function and export its output.`, )
})
return return
} }
const result = routes.reduce<{ const result = routes.reduce<{
bodyParserConfigRoutes: BodyParserConfigRoute[] bodyParserConfigRoutes: BodyParserConfigRoute[]
middleware: ScannedMiddlewareDescriptor[] middleware: MiddlewareDescriptor[]
}>( }>(
(result, route) => { (result, route) => {
if (!route.matcher) { if (!route.matcher) {
@@ -92,9 +77,15 @@ export class MiddlewareFileLoader {
const matcher = String(route.matcher) const matcher = String(route.matcher)
if ("bodyParser" in route && route.bodyParser !== undefined) { 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({ result.bodyParserConfigRoutes.push({
matcher: matcher, matcher: matcher,
method: route.method, methods,
config: route.bodyParser, config: route.bodyParser,
}) })
} }
@@ -104,7 +95,7 @@ export class MiddlewareFileLoader {
result.middleware.push({ result.middleware.push({
handler: middleware, handler: middleware,
matcher: matcher, matcher: matcher,
method: route.method, methods: route.methods,
}) })
}) })
} }
@@ -116,8 +107,16 @@ export class MiddlewareFileLoader {
} }
) )
this.#middleware = result.middleware const errorHandler =
this.#bodyParserConfigRoutes = result.bodyParserConfigRoutes 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`)) { } else if (await fs.exists(`${MIDDLEWARE_FILE_NAME}.js`)) {
await this.#processMiddlewareFile( 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 * Returns a collection of registered middleware
*/ */

View File

@@ -1,10 +1,18 @@
import { isObject, isPresent } from "@medusajs/utils" import { isObject, isPresent } from "@medusajs/utils"
import { MedusaNextFunction, MedusaRequest } from "../types" import type {
MedusaNextFunction,
MedusaRequest,
MedusaResponse,
} from "../types"
export function applyDefaultFilters<TFilter extends object>( export function applyDefaultFilters<TFilter extends object>(
filtersToApply: TFilter 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)) { for (const [filter, filterValue] of Object.entries(filtersToApply)) {
let valueToApply = filterValue let valueToApply = filterValue

View File

@@ -1,7 +1,15 @@
import { MedusaNextFunction, MedusaRequest } from "../types" import type {
MedusaNextFunction,
MedusaRequest,
MedusaResponse,
} from "../types"
export function applyParamsAsFilters(mappings: { [param: string]: string }) { 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)) { for (const [param, paramValue] of Object.entries(req.params)) {
if (mappings[param]) { if (mappings[param]) {
req.filterableFields[mappings[param]] = paramValue req.filterableFields[mappings[param]] = paramValue

View File

@@ -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
}
)
}

View File

@@ -1,7 +1,15 @@
import { MedusaNextFunction, MedusaRequest } from "../types" import type {
MedusaNextFunction,
MedusaRequest,
MedusaResponse,
} from "../types"
export function clearFiltersByKey(keys: string[]) { export function clearFiltersByKey(keys: string[]) {
return async (req: MedusaRequest, _, next: MedusaNextFunction) => { return async function clearFiltersByKeyMiddleware(
req: MedusaRequest,
_: MedusaResponse,
next: MedusaNextFunction
) {
keys.forEach((key) => { keys.forEach((key) => {
delete req.filterableFields[key] delete req.filterableFields[key]
}) })

View File

@@ -5,7 +5,7 @@ import {
MedusaError, MedusaError,
PUBLISHABLE_KEY_HEADER, PUBLISHABLE_KEY_HEADER,
} from "@medusajs/utils" } from "@medusajs/utils"
import { import type {
MedusaNextFunction, MedusaNextFunction,
MedusaResponse, MedusaResponse,
MedusaStoreRequest, MedusaStoreRequest,
@@ -13,7 +13,7 @@ import {
export async function ensurePublishableApiKeyMiddleware( export async function ensurePublishableApiKeyMiddleware(
req: MedusaStoreRequest, req: MedusaStoreRequest,
_res: MedusaResponse, _: MedusaResponse,
next: MedusaNextFunction next: MedusaNextFunction
) { ) {
const publishableApiKey = req.get(PUBLISHABLE_KEY_HEADER) const publishableApiKey = req.get(PUBLISHABLE_KEY_HEADER)

View File

@@ -1,4 +1,4 @@
import { NextFunction, Response } from "express" import { NextFunction, ErrorRequestHandler, Response } from "express"
import { ContainerRegistrationKeys, MedusaError } from "@medusajs/utils" import { ContainerRegistrationKeys, MedusaError } from "@medusajs/utils"
import { formatException } from "./exception-formatter" import { formatException } from "./exception-formatter"
@@ -13,12 +13,12 @@ const INVALID_REQUEST_ERROR = "invalid_request_error"
const INVALID_STATE_ERROR = "invalid_state_error" const INVALID_STATE_ERROR = "invalid_state_error"
export function errorHandler() { export function errorHandler() {
return ( return function coreErrorHandler(
err: MedusaError, err: MedusaError,
req: MedusaRequest, req: MedusaRequest,
res: Response, res: Response,
next: NextFunction _: NextFunction
) => { ) {
const logger = req.scope.resolve(ContainerRegistrationKeys.LOGGER) const logger = req.scope.resolve(ContainerRegistrationKeys.LOGGER)
err = formatException(err) err = formatException(err)
@@ -76,7 +76,7 @@ export function errorHandler() {
} }
res.status(statusCode).json(errObj) res.status(statusCode).json(errObj)
} } as unknown as ErrorRequestHandler
} }
/** /**

File diff suppressed because it is too large Load Diff

View File

@@ -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
}
}

View File

@@ -1,12 +1,7 @@
import { join, parse, sep } from "path" import { join, parse, sep } from "path"
import { dynamicImport, readDirRecursive } from "@medusajs/utils" import { dynamicImport, readDirRecursive } from "@medusajs/utils"
import { logger } from "../logger" import { logger } from "../logger"
import { import { type RouteVerb, HTTP_METHODS, type RouteDescriptor } from "./types"
type RouteVerb,
HTTP_METHODS,
type ScannedRouteDescriptor,
type FileSystemRouteDescriptor,
} from "./types"
/** /**
* File name that is used to indicate that the file is a route file * 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 STORE_ROUTE_MATCH = /(\/store$|\/store\/)/
const AUTH_ROUTE_MATCH = /(\/auth$|\/auth\/)/ 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 * Exposes to API to register routes manually or by scanning the filesystem from a
* source directory. * source directory.
@@ -69,19 +49,7 @@ export class RoutesLoader {
/** /**
* Routes collected manually or by scanning directories * Routes collected manually or by scanning directories
*/ */
#routes: Record< #routes: Record<string, Record<string, RouteDescriptor>> = {}
string,
Record<string, ScannedRouteDescriptor | FileSystemRouteDescriptor>
> = {}
/**
* An eventual activity id for information tracking
*/
readonly #activityId?: string
constructor({ activityId }: { activityId?: string }) {
this.#activityId = activityId
}
/** /**
* Creates the route path from its relative file path. * Creates the route path from its relative file path.
@@ -96,10 +64,9 @@ export class RoutesLoader {
if (segment.startsWith("[")) { if (segment.startsWith("[")) {
segment = segment.replace(PARAM_SEGMENT_MATCHER, (_, group) => { segment = segment.replace(PARAM_SEGMENT_MATCHER, (_, group) => {
if (params[group]) { if (params[group]) {
log({ logger.debug(
activityId: this.#activityId, `Duplicate parameters found in route ${relativePath} (${group})`
message: `Duplicate parameters found in route ${relativePath} (${group})`, )
})
throw new Error( throw new Error(
`Duplicate parameters found in route ${relativePath} (${group}). Make sure that all parameters are unique.` `Duplicate parameters found in route ${relativePath} (${group}). Make sure that all parameters are unique.`
@@ -122,7 +89,7 @@ export class RoutesLoader {
async #getRoutesForFile( async #getRoutesForFile(
routePath: string, routePath: string,
absolutePath: string absolutePath: string
): Promise<ScannedRouteDescriptor[]> { ): Promise<RouteDescriptor[]> {
const routeExports = await dynamicImport(absolutePath) const routeExports = await dynamicImport(absolutePath)
/** /**
@@ -161,10 +128,9 @@ export class RoutesLoader {
} }
if (!HTTP_METHODS.includes(key as RouteVerb)) { if (!HTTP_METHODS.includes(key as RouteVerb)) {
log({ logger.debug(
activityId: this.#activityId, `Skipping handler ${key} in ${absolutePath}. Invalid HTTP method: ${key}.`
message: `Skipping handler ${key} in ${absolutePath}. Invalid HTTP method: ${key}.`, )
})
return false return false
} }
@@ -172,14 +138,15 @@ export class RoutesLoader {
}) })
.map((key) => { .map((key) => {
return { return {
route: routePath, isRoute: true,
matcher: routePath,
method: key as RouteVerb, method: key as RouteVerb,
handler: routeExports[key], handler: routeExports[key],
optedOutOfAuth: !shouldAuthenticate, optedOutOfAuth: !shouldAuthenticate,
shouldAppendAdminCors: shouldApplyCors && routeType === "admin", shouldAppendAdminCors: shouldApplyCors && routeType === "admin",
shouldAppendAuthCors: shouldApplyCors && routeType === "auth", shouldAppendAuthCors: shouldApplyCors && routeType === "auth",
shouldAppendStoreCors: shouldApplyCors && routeType === "store", shouldAppendStoreCors: shouldApplyCors && routeType === "store",
} satisfies ScannedRouteDescriptor } satisfies RouteDescriptor
}) })
} }
@@ -232,18 +199,16 @@ export class RoutesLoader {
/** /**
* Register a route * Register a route
*/ */
registerRoute(route: ScannedRouteDescriptor | FileSystemRouteDescriptor) { registerRoute(route: RouteDescriptor) {
this.#routes[route.route] = this.#routes[route.route] ?? {} this.#routes[route.matcher] = this.#routes[route.matcher] ?? {}
const trackedRoute = this.#routes[route.route] const trackedRoute = this.#routes[route.matcher]
trackedRoute[route.method] = route trackedRoute[route.method] = route
} }
/** /**
* Register one or more routes * Register one or more routes
*/ */
registerRoutes( registerRoutes(routes: RouteDescriptor[]) {
routes: (ScannedRouteDescriptor | FileSystemRouteDescriptor)[]
) {
routes.forEach((route) => this.registerRoute(route)) routes.forEach((route) => this.registerRoute(route))
} }
@@ -252,14 +217,16 @@ export class RoutesLoader {
* manually. * manually.
*/ */
getRoutes() { getRoutes() {
return Object.keys(this.#routes).reduce< return Object.keys(this.#routes).reduce<RouteDescriptor[]>(
(ScannedRouteDescriptor | FileSystemRouteDescriptor)[] (result, routePattern) => {
>((result, routePattern) => { const methodsRoutes = this.#routes[routePattern]
const methodsRoutes = this.#routes[routePattern] Object.keys(methodsRoutes).forEach((method) => {
Object.keys(methodsRoutes).forEach((method) => { const route = methodsRoutes[method]
result.push(methodsRoutes[method]) result.push(route)
}) })
return result return result
}, []) },
[]
)
} }
} }

View File

@@ -1,9 +1,9 @@
import { MiddlewareVerb } from "./types" import { MiddlewareVerb, RouteVerb } from "./types"
/** /**
* Route represents both the middleware/routes defined via the * Route represents both the middleware/routes defined via the
* "defineMiddlewares" method and the routes scanned from * "defineMiddlewares" method and the routes scanned from
* the filesystem. The later one's must be marked with "isAppRoute = true". * the filesystem.
*/ */
type Route = { type Route = {
/** /**
@@ -17,10 +17,9 @@ type Route = {
handler?: any handler?: any
/** /**
* Must be true when the route is discovered via the fileystem * The HTTP method specified as a single value
* scanning.
*/ */
isAppRoute?: boolean method?: RouteVerb
/** /**
* The HTTP methods this route is supposed to handle. * The HTTP methods this route is supposed to handle.
@@ -48,39 +47,39 @@ type Route = {
* - static * - static
* - params * - params
*/ */
type RoutesBranch = { type RoutesBranch<T extends Route> = {
global: { global: {
routes: Route[] routes: T[]
children?: { children?: {
[segment: string]: RoutesBranch [segment: string]: RoutesBranch<T>
} }
} }
regex: { regex: {
routes: Route[] routes: T[]
children?: { children?: {
[segment: string]: RoutesBranch [segment: string]: RoutesBranch<T>
} }
} }
wildcard: { wildcard: {
routes: Route[] routes: T[]
children?: { children?: {
[segment: string]: RoutesBranch [segment: string]: RoutesBranch<T>
} }
} }
params: { params: {
routes: Route[] routes: T[]
children?: { children?: {
[segment: string]: RoutesBranch [segment: string]: RoutesBranch<T>
} }
} }
static: { static: {
routes: Route[] routes: T[]
children?: { 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 * like structure and then sort them back to a flat array based upon the
* priorities of different types of nodes. * 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 * Input routes
*/ */
#routesToProcess: Route[] #routesToProcess: T[]
/** /**
* Intermediate tree representation for sorting routes * Intermediate tree representation for sorting routes
*/ */
#routesTree: { #routesTree: {
[segment: string]: RoutesBranch [segment: string]: RoutesBranch<T>
} = { } = {
root: this.#createBranch(), root: this.#createBranch(),
} }
constructor(routes: Route[]) { constructor(routes: T[]) {
this.#routesToProcess = routes this.#routesToProcess = routes
} }
/** /**
* Creates an empty branch with known nodes * Creates an empty branch with known nodes
*/ */
#createBranch(): RoutesBranch { #createBranch(): RoutesBranch<T> {
return { return {
global: { global: {
routes: [], routes: [],
@@ -162,14 +174,14 @@ export class RoutesSorter {
* } * }
* ``` * ```
*/ */
#processRoute(route: Route) { #processRoute(route: T) {
const segments = route.matcher.split("/").filter((s) => s.length) const segments = route.matcher.split("/").filter((s) => s.length)
let parent = this.#routesTree["root"] let parent = this.#routesTree["root"]
segments.forEach((segment, index) => { 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" bucket = "global"
} else if (segment.startsWith("*")) { } else if (segment.startsWith("*")) {
bucket = "wildcard" bucket = "wildcard"
@@ -194,40 +206,51 @@ export class RoutesSorter {
/** /**
* Returns an array of sorted routes for a given branch. * 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<{ const branchRoutes = Object.keys(routeBranch).reduce<{
global: Route[] global: T[]
wildcard: Route[] wildcard: T[]
regex: Route[] regex: T[]
params: Route[] params: T[]
static: Route[] static: T[]
}>( }>(
(result, branchKey) => { (result, branchKey) => {
const node = routeBranch[branchKey] const node = routeBranch[branchKey]
result.global.push(...node.global.routes) result.global.push(...node.global.routes)
if (node.global.children) { 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) result.wildcard.push(...node.wildcard.routes)
if (node.wildcard.children) { 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) result.regex.push(...node.regex.routes)
if (node.regex.children) { 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) result.static.push(...node.static.routes)
if (node.static.children) { 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) result.params.push(...node.params.routes)
if (node.params.children) { if (node.params.children) {
result.params.push(...this.#sortBranch(node.params.children)) result.params.push(...this.#sortBranch(node.params.children, orderBy))
} }
return result return result
@@ -244,19 +267,33 @@ export class RoutesSorter {
/** /**
* Concatenating routes as per their priority. * Concatenating routes as per their priority.
*/ */
const routes: Route[] = branchRoutes.global return orderBy.reduce<T[]>((result, branch) => {
.concat(branchRoutes.wildcard) result = result.concat(branchRoutes[branch])
.concat(branchRoutes.regex) return result
.concat(branchRoutes.static) }, [])
.concat(branchRoutes.params)
return routes
} }
/** /**
* 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)) this.#routesToProcess.map((route) => this.#processRoute(route))
return this.#sortBranch(this.#routesTree) return this.#sortBranch(this.#routesTree, orderBy ?? this.#orderBy)
} }
} }

View File

@@ -34,20 +34,6 @@ export type AsyncRouteHandler = (
export type RouteHandler = SyncRouteHandler | 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 = export type MiddlewareFunction =
| MedusaRequestHandler | MedusaRequestHandler
| ((...args: any[]) => any) | ((...args: any[]) => any)
@@ -67,7 +53,11 @@ export type ParserConfigArgs = {
export type ParserConfig = false | ParserConfigArgs export type ParserConfig = false | ParserConfigArgs
export type MiddlewareRoute = { export type MiddlewareRoute = {
/**
* @deprecated. Instead use {@link MiddlewareRoute.methods}
*/
method?: MiddlewareVerb | MiddlewareVerb[] method?: MiddlewareVerb | MiddlewareVerb[]
methods?: MiddlewareVerb[]
matcher: string | RegExp matcher: string | RegExp
bodyParser?: ParserConfig bodyParser?: ParserConfig
middlewares?: MiddlewareFunction[] middlewares?: MiddlewareFunction[]
@@ -78,49 +68,38 @@ export type MiddlewaresConfig = {
routes?: MiddlewareRoute[] routes?: MiddlewareRoute[]
} }
export type RouteDescriptor = {
absolutePath: string
relativePath: string
route: string
priority: number
config?: RouteConfig
}
/** /**
* Route descriptor refers represents a route either scanned * Route descriptor refers represents a route either scanned
* from the filesystem or registered manually. It does not * from the filesystem or registered manually. It does not
* represent a middleware * represent a middleware
*/ */
export type ScannedRouteDescriptor = { export type RouteDescriptor = {
route: string matcher: string
method: RouteVerb method: RouteVerb
handler: RouteHandler handler: RouteHandler
optedOutOfAuth: boolean optedOutOfAuth: boolean
isRoute: true
routeType?: "admin" | "store" | "auth" routeType?: "admin" | "store" | "auth"
absolutePath?: string
relativePath?: string
shouldAppendAdminCors: boolean shouldAppendAdminCors: boolean
shouldAppendStoreCors: boolean shouldAppendStoreCors: boolean
shouldAppendAuthCors: boolean shouldAppendAuthCors: boolean
} }
/** /**
* FileSystem route description represents a route scanned from * Represents a middleware
* the filesystem
*/ */
export type FileSystemRouteDescriptor = ScannedRouteDescriptor & { export type MiddlewareDescriptor = {
absolutePath: string
relativePath: string
}
export type ScannedMiddlewareDescriptor = {
matcher: string matcher: string
method?: MiddlewareVerb | MiddlewareVerb[] methods?: MiddlewareVerb | MiddlewareVerb[]
handler: MiddlewareFunction handler: MiddlewareFunction
} }
export type BodyParserConfigRoute = { export type BodyParserConfigRoute = {
matcher: string matcher: string
method?: MiddlewareVerb | MiddlewareVerb[] methods: MiddlewareVerb | MiddlewareVerb[]
config?: ParserConfig config: ParserConfig
} }
export type GlobalMiddlewareDescriptor = { export type GlobalMiddlewareDescriptor = {

View File

@@ -16,7 +16,11 @@ import zod, { ZodRawShape } from "zod"
*/ */
export function defineMiddlewares< export function defineMiddlewares<
Route extends { Route extends {
/**
* @deprecated. Instead use {@link MiddlewareRoute.methods}
*/
method?: MiddlewareVerb | MiddlewareVerb[] method?: MiddlewareVerb | MiddlewareVerb[]
methods?: MiddlewareVerb[]
matcher: string | RegExp matcher: string | RegExp
bodyParser?: ParserConfig bodyParser?: ParserConfig
additionalDataValidator?: ZodRawShape additionalDataValidator?: ZodRawShape
@@ -38,7 +42,8 @@ export function defineMiddlewares<
return { return {
errorHandler, errorHandler,
routes: routes.map((route) => { routes: routes.map((route) => {
const { middlewares, additionalDataValidator, ...rest } = route let { middlewares, method, methods, additionalDataValidator, ...rest } =
route
const customMiddleware: MedusaRequestHandler[] = [] const customMiddleware: MedusaRequestHandler[] = []
/** /**
@@ -54,8 +59,13 @@ export function defineMiddlewares<
}) })
} }
if (!methods) {
methods = Array.isArray(method) ? method : method ? [method] : method
}
return { return {
...rest, ...rest,
methods,
middlewares: customMiddleware.concat(middlewares || []), middlewares: customMiddleware.concat(middlewares || []),
} }
}), }),

View File

@@ -3,7 +3,11 @@ import {
ContainerRegistrationKeys, ContainerRegistrationKeys,
remoteQueryObjectFromString, remoteQueryObjectFromString,
} from "@medusajs/utils" } from "@medusajs/utils"
import { MedusaNextFunction, MedusaRequest } from "../types" import type {
MedusaNextFunction,
MedusaRequest,
MedusaResponse,
} from "../types"
export function maybeApplyLinkFilter({ export function maybeApplyLinkFilter({
entryPoint, entryPoint,
@@ -13,7 +17,7 @@ export function maybeApplyLinkFilter({
}) { }) {
return async function linkFilter( return async function linkFilter(
req: MedusaRequest, req: MedusaRequest,
_, _: MedusaResponse,
next: MedusaNextFunction next: MedusaNextFunction
) { ) {
const filterableFields = req.filterableFields const filterableFields = req.filterableFields

View File

@@ -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
}

View File

@@ -2,7 +2,7 @@ import {
validateAndTransformBody, validateAndTransformBody,
validateAndTransformQuery, validateAndTransformQuery,
} from "@medusajs/framework" } from "@medusajs/framework"
import { MiddlewareRoute, unlessPath } from "@medusajs/framework/http" import { MiddlewareRoute } from "@medusajs/framework/http"
import { DEFAULT_BATCH_ENDPOINTS_SIZE_LIMIT } from "../../../utils/middlewares" import { DEFAULT_BATCH_ENDPOINTS_SIZE_LIMIT } from "../../../utils/middlewares"
import * as QueryConfig from "./query-config" import * as QueryConfig from "./query-config"
import { import {
@@ -116,12 +116,9 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
method: ["DELETE"], method: ["DELETE"],
matcher: "/admin/inventory-items/:id/location-levels/:location_id", matcher: "/admin/inventory-items/:id/location-levels/:location_id",
middlewares: [ middlewares: [
unlessPath( validateAndTransformQuery(
/.*\/location-levels\/batch/, AdminGetInventoryItemParams,
validateAndTransformQuery( QueryConfig.retrieveTransformQueryConfig
AdminGetInventoryItemParams,
QueryConfig.retrieveTransformQueryConfig
)
), ),
], ],
}, },
@@ -129,16 +126,10 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
method: ["POST"], method: ["POST"],
matcher: "/admin/inventory-items/:id/location-levels/:location_id", matcher: "/admin/inventory-items/:id/location-levels/:location_id",
middlewares: [ middlewares: [
unlessPath( validateAndTransformBody(AdminUpdateInventoryLocationLevel),
/.*\/location-levels\/batch/, validateAndTransformQuery(
validateAndTransformBody(AdminUpdateInventoryLocationLevel) AdminGetInventoryItemParams,
), QueryConfig.retrieveTransformQueryConfig
unlessPath(
/.*\/location-levels\/batch/,
validateAndTransformQuery(
AdminGetInventoryItemParams,
QueryConfig.retrieveTransformQueryConfig
)
), ),
], ],
}, },

View File

@@ -1,4 +1,4 @@
import { MiddlewareRoute, unlessPath } from "@medusajs/framework/http" import { MiddlewareRoute } from "@medusajs/framework/http"
import { import {
validateAndTransformBody, validateAndTransformBody,
validateAndTransformQuery, validateAndTransformQuery,
@@ -37,12 +37,9 @@ export const adminPaymentRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/admin/payments/:id", matcher: "/admin/payments/:id",
middlewares: [ middlewares: [
unlessPath( validateAndTransformQuery(
/.*\/payments\/payment-providers/, AdminGetPaymentParams,
validateAndTransformQuery( queryConfig.retrieveTransformQueryConfig
AdminGetPaymentParams,
queryConfig.retrieveTransformQueryConfig
)
), ),
], ],
}, },

View File

@@ -3,11 +3,7 @@ import {
validateAndTransformBody, validateAndTransformBody,
validateAndTransformQuery, validateAndTransformQuery,
} from "@medusajs/framework" } from "@medusajs/framework"
import { import { maybeApplyLinkFilter, MiddlewareRoute } from "@medusajs/framework/http"
maybeApplyLinkFilter,
MiddlewareRoute,
unlessPath,
} from "@medusajs/framework/http"
import multer from "multer" import multer from "multer"
import { DEFAULT_BATCH_ENDPOINTS_SIZE_LIMIT } from "../../../utils/middlewares" import { DEFAULT_BATCH_ENDPOINTS_SIZE_LIMIT } from "../../../utils/middlewares"
import { createBatchBody } from "../../utils/validators" import { createBatchBody } from "../../utils/validators"
@@ -117,12 +113,9 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/admin/products/:id", matcher: "/admin/products/:id",
middlewares: [ middlewares: [
unlessPath( validateAndTransformQuery(
/.*\/products\/(batch|export|import)/, AdminGetProductParams,
validateAndTransformQuery( QueryConfig.retrieveProductQueryConfig
AdminGetProductParams,
QueryConfig.retrieveProductQueryConfig
)
), ),
], ],
}, },
@@ -130,16 +123,10 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
method: ["POST"], method: ["POST"],
matcher: "/admin/products/:id", matcher: "/admin/products/:id",
middlewares: [ middlewares: [
unlessPath( validateAndTransformBody(AdminUpdateProduct),
/.*\/products\/(batch|export|import)/, validateAndTransformQuery(
validateAndTransformBody(AdminUpdateProduct) AdminGetProductParams,
), QueryConfig.retrieveProductQueryConfig
unlessPath(
/.*\/products\/(batch|export|import)/,
validateAndTransformQuery(
AdminGetProductParams,
QueryConfig.retrieveProductQueryConfig
)
), ),
], ],
}, },
@@ -147,12 +134,9 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
method: ["DELETE"], method: ["DELETE"],
matcher: "/admin/products/:id", matcher: "/admin/products/:id",
middlewares: [ middlewares: [
unlessPath( validateAndTransformQuery(
/.*\/products\/(batch|export|import)/, AdminGetProductParams,
validateAndTransformQuery( QueryConfig.retrieveProductQueryConfig
AdminGetProductParams,
QueryConfig.retrieveProductQueryConfig
)
), ),
], ],
}, },
@@ -198,12 +182,9 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/admin/products/:id/variants/:variant_id", matcher: "/admin/products/:id/variants/:variant_id",
middlewares: [ middlewares: [
unlessPath( validateAndTransformQuery(
/.*\/variants\/batch/, AdminGetProductVariantParams,
validateAndTransformQuery( QueryConfig.retrieveVariantConfig
AdminGetProductVariantParams,
QueryConfig.retrieveVariantConfig
)
), ),
], ],
}, },
@@ -211,16 +192,10 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
method: ["POST"], method: ["POST"],
matcher: "/admin/products/:id/variants/:variant_id", matcher: "/admin/products/:id/variants/:variant_id",
middlewares: [ middlewares: [
unlessPath( validateAndTransformBody(AdminUpdateProductVariant),
/.*\/variants\/batch/, validateAndTransformQuery(
validateAndTransformBody(AdminUpdateProductVariant) AdminGetProductParams,
), QueryConfig.retrieveProductQueryConfig
unlessPath(
/.*\/variants\/batch/,
validateAndTransformQuery(
AdminGetProductParams,
QueryConfig.retrieveProductQueryConfig
)
), ),
], ],
}, },
@@ -228,12 +203,9 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
method: ["DELETE"], method: ["DELETE"],
matcher: "/admin/products/:id/variants/:variant_id", matcher: "/admin/products/:id/variants/:variant_id",
middlewares: [ middlewares: [
unlessPath( validateAndTransformQuery(
/.*\/variants\/batch/, AdminGetProductParams,
validateAndTransformQuery( QueryConfig.retrieveProductQueryConfig
AdminGetProductParams,
QueryConfig.retrieveProductQueryConfig
)
), ),
], ],
}, },

View File

@@ -2,7 +2,7 @@ import {
validateAndTransformBody, validateAndTransformBody,
validateAndTransformQuery, validateAndTransformQuery,
} from "@medusajs/framework" } from "@medusajs/framework"
import { MiddlewareRoute, unlessPath } from "@medusajs/framework/http" import { MiddlewareRoute } from "@medusajs/framework/http"
import { DEFAULT_BATCH_ENDPOINTS_SIZE_LIMIT } from "../../../utils/middlewares" import { DEFAULT_BATCH_ENDPOINTS_SIZE_LIMIT } from "../../../utils/middlewares"
import { createBatchBody } from "../../utils/validators" import { createBatchBody } from "../../utils/validators"
import * as QueryConfig from "./query-config" import * as QueryConfig from "./query-config"
@@ -65,12 +65,9 @@ export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/admin/promotions/:id/:rule_type", matcher: "/admin/promotions/:id/:rule_type",
middlewares: [ middlewares: [
unlessPath( validateAndTransformQuery(
/.*\/promotions\/rule-attribute-options/, AdminGetPromotionRuleTypeParams,
validateAndTransformQuery( QueryConfig.retrieveTransformQueryConfig
AdminGetPromotionRuleTypeParams,
QueryConfig.retrieveTransformQueryConfig
)
), ),
], ],
}, },

View File

@@ -44,18 +44,18 @@ export const adminWorkflowsExecutionsMiddlewares: MiddlewareRoute[] = [
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/workflows-executions/:id/run", matcher: "/admin/workflows-executions/:workflow_id/run",
middlewares: [validateAndTransformBody(AdminCreateWorkflowsRun)], middlewares: [validateAndTransformBody(AdminCreateWorkflowsRun)],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/workflows-executions/:id/steps/success", matcher: "/admin/workflows-executions/:workflow_id/steps/success",
middlewares: [validateAndTransformBody(AdminCreateWorkflowsAsyncResponse)], middlewares: [validateAndTransformBody(AdminCreateWorkflowsAsyncResponse)],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/workflows-executions/:id/steps/failure", matcher: "/admin/workflows-executions/:workflow_id/steps/failure",
middlewares: [validateAndTransformBody(AdminCreateWorkflowsAsyncResponse)], middlewares: [validateAndTransformBody(AdminCreateWorkflowsAsyncResponse)],
}, },
] ]

View File

@@ -5,7 +5,7 @@ import {
MedusaResponse, MedusaResponse,
Query, Query,
} from "@medusajs/framework" } from "@medusajs/framework"
import { ApiRoutesLoader } from "@medusajs/framework/http" import { ApiLoader } from "@medusajs/framework/http"
import { Tracer } from "@medusajs/framework/telemetry" import { Tracer } from "@medusajs/framework/telemetry"
import type { SpanExporter } from "@opentelemetry/sdk-trace-node" import type { SpanExporter } from "@opentelemetry/sdk-trace-node"
import type { NodeSDKConfiguration } from "@opentelemetry/sdk-node" import type { NodeSDKConfiguration } from "@opentelemetry/sdk-node"
@@ -66,7 +66,7 @@ export function instrumentHttpLayer() {
* Instrumenting the route handler to report traces to * Instrumenting the route handler to report traces to
* OpenTelemetry * OpenTelemetry
*/ */
ApiRoutesLoader.traceRoute = (handler) => { ApiLoader.traceRoute = (handler) => {
return async (req, res) => { return async (req, res) => {
if (shouldExcludeResource(req.originalUrl)) { if (shouldExcludeResource(req.originalUrl)) {
return await handler(req, res) return await handler(req, res)
@@ -95,7 +95,7 @@ export function instrumentHttpLayer() {
* Instrumenting the middleware handler to report traces to * Instrumenting the middleware handler to report traces to
* OpenTelemetry * OpenTelemetry
*/ */
ApiRoutesLoader.traceMiddleware = (handler) => { ApiLoader.traceMiddleware = (handler) => {
return async ( return async (
req: MedusaRequest<any>, req: MedusaRequest<any>,
res: MedusaResponse, res: MedusaResponse,

View File

@@ -1,7 +1,7 @@
import { Express } from "express" import { Express } from "express"
import { join } from "path" import { join } from "path"
import qs from "qs" import qs from "qs"
import { RoutesLoader } from "@medusajs/framework/http" import { ApiLoader } from "@medusajs/framework/http"
import { MedusaContainer, PluginDetails } from "@medusajs/framework/types" import { MedusaContainer, PluginDetails } from "@medusajs/framework/types"
import { ConfigModule } from "@medusajs/framework/config" import { ConfigModule } from "@medusajs/framework/config"
@@ -39,13 +39,10 @@ export default async ({ app, container, plugins }: Options) => {
* "/products/:id" route. * "/products/:id" route.
*/ */
sourcePaths.push( sourcePaths.push(
join(__dirname, "../api"),
...plugins.map((pluginDetails) => { ...plugins.map((pluginDetails) => {
return join(pluginDetails.resolve, "api") return join(pluginDetails.resolve, "api")
}), })
/**
* Register the Medusa CORE API routes using the file based routing.
*/
join(__dirname, "../api")
) )
const { const {
@@ -58,7 +55,7 @@ export default async ({ app, container, plugins }: Options) => {
// Adding this here temporarily // Adding this here temporarily
// Test: (packages/medusa/src/api/routes/admin/currencies/update-currency.ts) // Test: (packages/medusa/src/api/routes/admin/currencies/update-currency.ts)
try { try {
await new RoutesLoader({ await new ApiLoader({
app: app, app: app,
sourceDir: sourcePaths, sourceDir: sourcePaths,
baseRestrictedFields: restrictedFields?.store, baseRestrictedFields: restrictedFields?.store,

View File

@@ -5792,6 +5792,7 @@ __metadata:
jsonwebtoken: ^9.0.2 jsonwebtoken: ^9.0.2
lodash: 4.17.21 lodash: 4.17.21
morgan: ^1.9.1 morgan: ^1.9.1
path-to-regexp: ^0.1.10
pg: ^8.13.0 pg: ^8.13.0
rimraf: ^3.0.2 rimraf: ^3.0.2
supertest: ^4.0.2 supertest: ^4.0.2
@@ -26955,6 +26956,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"path-to-regexp@npm:^0.1.10":
version: 0.1.12
resolution: "path-to-regexp@npm:0.1.12"
checksum: 1c6ff10ca169b773f3bba943bbc6a07182e332464704572962d277b900aeee81ac6aa5d060ff9e01149636c30b1f63af6e69dd7786ba6e0ddb39d4dee1f0645b
languageName: node
linkType: hard
"path-to-regexp@npm:^6.2.0": "path-to-regexp@npm:^6.2.0":
version: 6.2.2 version: 6.2.2
resolution: "path-to-regexp@npm:6.2.2" resolution: "path-to-regexp@npm:6.2.2"