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",
"lodash": "4.17.21",
"morgan": "^1.9.1",
"path-to-regexp": "^0.1.10",
"tsconfig-paths": "^4.2.0",
"zod": "3.22.4"
},

View File

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

View File

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

View File

@@ -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",
],
},
]
`)

View File

@@ -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."
)

View File

@@ -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",
],
},
]
`)
})
})

View File

@@ -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
*/

View File

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

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

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[]) {
return async (req: MedusaRequest, _, next: MedusaNextFunction) => {
return async function clearFiltersByKeyMiddleware(
req: MedusaRequest,
_: MedusaResponse,
next: MedusaNextFunction
) {
keys.forEach((key) => {
delete req.filterableFields[key]
})

View File

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

View File

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

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 { 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
},
[]
)
}
}

View File

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

View File

@@ -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 = {

View File

@@ -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 || []),
}
}),

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5792,6 +5792,7 @@ __metadata:
jsonwebtoken: ^9.0.2
lodash: 4.17.21
morgan: ^1.9.1
path-to-regexp: ^0.1.10
pg: ^8.13.0
rimraf: ^3.0.2
supertest: ^4.0.2
@@ -26955,6 +26956,13 @@ __metadata:
languageName: node
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":
version: 6.2.2
resolution: "path-to-regexp@npm:6.2.2"