chore: Start cleaning up medusa-core-utils (#7450)
**What** - remove medusa-core-utils - dispatch the utils where they belongs - update usage **NOTE** I have been wondering if the graceful class should go into the utils package or medusa package, I ve put it in the medusa package as it seems to be the best place I can see for now and is tight to the server as well. Also, I wanted to avoid the utils package to depends on http and net dependencies, happy to change that if you feel like it
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { ConfigModule } from "@medusajs/types"
|
||||
import { transformFile } from "@swc/core"
|
||||
import { getConfigFile } from "medusa-core-utils"
|
||||
import { getConfigFile } from "@medusajs/utils"
|
||||
import { existsSync } from "node:fs"
|
||||
import { copyFile, mkdir, readdir, rm, writeFile } from "node:fs/promises"
|
||||
import path from "path"
|
||||
|
||||
@@ -3,7 +3,7 @@ import "regenerator-runtime/runtime"
|
||||
|
||||
import cluster from "cluster"
|
||||
import express from "express"
|
||||
import { GracefulShutdownServer } from "medusa-core-utils"
|
||||
import { GracefulShutdownServer } from "../utils"
|
||||
import { track } from "medusa-telemetry"
|
||||
import { scheduleJob } from "node-schedule"
|
||||
import os from "os"
|
||||
|
||||
@@ -2,7 +2,7 @@ import "core-js/stable"
|
||||
import "regenerator-runtime/runtime"
|
||||
|
||||
import express from "express"
|
||||
import { GracefulShutdownServer } from "medusa-core-utils"
|
||||
import { GracefulShutdownServer } from "../utils"
|
||||
import { track } from "medusa-telemetry"
|
||||
import { scheduleJob } from "node-schedule"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MedusaModule, registerMedusaModule } from "@medusajs/modules-sdk"
|
||||
import glob from "glob"
|
||||
import { isDefined } from "medusa-core-utils"
|
||||
import { isDefined } from "@medusajs/utils"
|
||||
|
||||
export function getInternalModules(configModule) {
|
||||
const modules = []
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ConfigModule } from "@medusajs/types"
|
||||
import { getConfigFile, isDefined } from "medusa-core-utils"
|
||||
import { getConfigFile, isDefined } from "@medusajs/utils"
|
||||
import logger from "./logger"
|
||||
|
||||
const isProduction = ["production", "prod"].includes(process.env.NODE_ENV || "")
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
objectFromStringPath,
|
||||
} from "@medusajs/utils"
|
||||
import glob from "glob"
|
||||
import { isDefined } from "medusa-core-utils"
|
||||
import { isDefined } from "@medusajs/utils"
|
||||
import { trackFeatureFlag } from "medusa-telemetry"
|
||||
import path from "path"
|
||||
import { FlagSettings } from "../../types/feature-flags"
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ConfigModule, PluginDetails } from "@medusajs/types"
|
||||
import { isString } from "@medusajs/utils"
|
||||
import fs from "fs"
|
||||
import { sync as existsSync } from "fs-exists-cached"
|
||||
import { createRequireFromPath } from "medusa-core-utils"
|
||||
import path from "path"
|
||||
|
||||
export const MEDUSA_PROJECT_NAME = "project-plugin"
|
||||
@@ -57,23 +56,14 @@ function resolvePlugin(pluginName: string): {
|
||||
}
|
||||
}
|
||||
|
||||
const rootDir = path.resolve(".")
|
||||
|
||||
/**
|
||||
* Here we have an absolute path to an internal plugin, or a name of a module
|
||||
* which should be located in node_modules.
|
||||
*/
|
||||
try {
|
||||
const requireSource =
|
||||
rootDir !== null
|
||||
? createRequireFromPath(`${rootDir}/:internal:`)
|
||||
: require
|
||||
|
||||
// If the path is absolute, resolve the directory of the internal plugin,
|
||||
// otherwise resolve the directory containing the package.json
|
||||
const resolvedPath = path.dirname(
|
||||
requireSource.resolve(`${pluginName}/package.json`)
|
||||
)
|
||||
const resolvedPath = require.resolve(pluginName)
|
||||
|
||||
const packageJSON = JSON.parse(
|
||||
fs.readFileSync(`${resolvedPath}/package.json`, `utf-8`)
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { Request, Response } from "express"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
export const GET = async (req: Request, res: Response) => {
|
||||
throw new MedusaError(MedusaError.Types.NOT_ALLOWED, "Not allowed")
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { asValue } from "awilix"
|
||||
import express from "express"
|
||||
import jwt from "jsonwebtoken"
|
||||
import { MockManager } from "medusa-test-utils"
|
||||
import querystring from "querystring"
|
||||
import supertest from "supertest"
|
||||
import apiLoader from "../../../../api"
|
||||
@@ -70,7 +69,7 @@ export const createServer = async (rootDir) => {
|
||||
logger: asValue({
|
||||
error: () => {},
|
||||
}),
|
||||
manager: asValue(MockManager),
|
||||
manager: asValue({}),
|
||||
})
|
||||
|
||||
app.set("trust proxy", 1)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import express from "express"
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { resolve } from "path"
|
||||
import {
|
||||
config,
|
||||
@@ -32,7 +31,7 @@ describe("RoutesLoader", function () {
|
||||
const res = await request("GET", "/admin/orders/1000", {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
userId: "admin_user",
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -45,7 +44,7 @@ describe("RoutesLoader", function () {
|
||||
const res = await request("POST", "/admin/orders/1000", {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
userId: "admin_user",
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -132,7 +131,7 @@ describe("RoutesLoader", function () {
|
||||
const res = await request("GET", "/admin/protected", {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
userId: "admin_user",
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -159,7 +158,7 @@ describe("RoutesLoader", function () {
|
||||
const res = await request("GET", "/store/me/protected", {
|
||||
clientSession: {
|
||||
jwt: {
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
customer_id: "lebron",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { ConfigModule } from "@medusajs/types"
|
||||
import { promiseAll, wrapHandler } from "@medusajs/utils"
|
||||
import { parseCorsOrigins, promiseAll, wrapHandler } from "@medusajs/utils"
|
||||
import cors from "cors"
|
||||
import { Router, json, text, urlencoded, type Express } from "express"
|
||||
import { type Express, json, Router, text, urlencoded } from "express"
|
||||
import { readdir } from "fs/promises"
|
||||
import { parseCorsOrigins } from "medusa-core-utils"
|
||||
import { extname, join, sep } from "path"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
|
||||
import { errorHandler } from "../../../utils/middlewares"
|
||||
@@ -13,8 +12,8 @@ import {
|
||||
GlobalMiddlewareDescriptor,
|
||||
HTTP_METHODS,
|
||||
MiddlewareRoute,
|
||||
MiddlewareVerb,
|
||||
MiddlewaresConfig,
|
||||
MiddlewareVerb,
|
||||
ParserConfigArgs,
|
||||
RouteConfig,
|
||||
RouteDescriptor,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ContainerRegistrationKeys, promiseAll } from "@medusajs/utils"
|
||||
import { asValue } from "awilix"
|
||||
import { Express, NextFunction, Request, Response } from "express"
|
||||
import glob from "glob"
|
||||
import { createMedusaContainer } from "medusa-core-utils"
|
||||
import { createMedusaContainer } from "@medusajs/utils"
|
||||
import path from "path"
|
||||
import requestIp from "request-ip"
|
||||
import { v4 } from "uuid"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { RequestQueryFields } from "@medusajs/types"
|
||||
import { MedusaContainer as coreMedusaContainer } from "medusa-core-utils"
|
||||
import {
|
||||
MedusaContainer as coreMedusaContainer,
|
||||
RequestQueryFields,
|
||||
} from "@medusajs/types"
|
||||
import { FindConfig } from "./common"
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
import { GracefulShutdownServer } from "../graceful-shutdown-server"
|
||||
|
||||
describe("GracefulShutdownServer", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllTimers()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
afterEach(() => {})
|
||||
|
||||
it('should add "isShuttingDown" property to the existing server', () => {
|
||||
const server = GracefulShutdownServer.create({ on: jest.fn() } as any)
|
||||
expect(server).toHaveProperty("isShuttingDown")
|
||||
expect(server.isShuttingDown).toEqual(false)
|
||||
})
|
||||
|
||||
it("should listen for client connections and store reference to them", async () => {
|
||||
const onEventMock = jest.fn()
|
||||
|
||||
GracefulShutdownServer.create({ on: onEventMock } as any)
|
||||
|
||||
expect(onEventMock).toBeCalledTimes(3)
|
||||
|
||||
expect(onEventMock.mock.calls[2][0]).toEqual("request")
|
||||
|
||||
const connectEvent: (socket) => any = onEventMock.mock.calls[0][1]
|
||||
|
||||
const onSocketClose = jest.fn()
|
||||
const socket = { on: onSocketClose }
|
||||
connectEvent(socket)
|
||||
expect(socket).toEqual(
|
||||
expect.objectContaining({
|
||||
_idle: true,
|
||||
_connectionId: 1,
|
||||
})
|
||||
)
|
||||
|
||||
const socket2 = { on: onSocketClose }
|
||||
connectEvent(socket2)
|
||||
expect(socket2).toEqual(
|
||||
expect.objectContaining({
|
||||
_idle: true,
|
||||
_connectionId: 2,
|
||||
})
|
||||
)
|
||||
|
||||
const requestMock = onEventMock.mock.calls[2][1]
|
||||
expect(typeof requestMock).toEqual("function")
|
||||
|
||||
const socket3 = { on: onSocketClose }
|
||||
const req = { socket: socket3, on: jest.fn() }
|
||||
const res = { on: jest.fn() }
|
||||
connectEvent(socket3)
|
||||
requestMock(req, res)
|
||||
|
||||
const finishRequestMock = res.on.mock.calls[0][1]
|
||||
|
||||
expect(socket3).toEqual(
|
||||
expect.objectContaining({
|
||||
_idle: false,
|
||||
_connectionId: 3,
|
||||
})
|
||||
)
|
||||
finishRequestMock()
|
||||
expect(socket3).toEqual(
|
||||
expect.objectContaining({
|
||||
_idle: true,
|
||||
_connectionId: 3,
|
||||
})
|
||||
)
|
||||
|
||||
expect(onSocketClose).toBeCalledTimes(3)
|
||||
expect(onSocketClose.mock.calls[0][0]).toEqual("close")
|
||||
})
|
||||
|
||||
it("waits requests to complete before shutting the server down", (done: Function) => {
|
||||
jest.useFakeTimers()
|
||||
|
||||
const onEventMock = jest.fn()
|
||||
const setIntervalSpy = jest.spyOn(global, "setInterval")
|
||||
const setTimeoutSpy = jest.spyOn(global, "setTimeout")
|
||||
const clearIntervalSpy = jest.spyOn(global, "clearInterval")
|
||||
|
||||
const waitTime = 200
|
||||
let closeServerCallback: Function
|
||||
const server = GracefulShutdownServer.create(
|
||||
{
|
||||
close: (callback) => {
|
||||
closeServerCallback = callback
|
||||
},
|
||||
on: onEventMock,
|
||||
} as any,
|
||||
waitTime
|
||||
)
|
||||
|
||||
const requestMock = onEventMock.mock.calls[2][1]
|
||||
const connectEvent: (socket) => any = onEventMock.mock.calls[0][1]
|
||||
|
||||
expect(typeof requestMock).toEqual("function")
|
||||
|
||||
const socket = { on: jest.fn(), destroy: jest.fn() }
|
||||
const req = { socket, on: jest.fn() }
|
||||
const res = { on: jest.fn() }
|
||||
connectEvent(socket)
|
||||
|
||||
requestMock(req, res)
|
||||
|
||||
const finishRequestMock = res.on.mock.calls[0][1]
|
||||
|
||||
server.shutdown().then(() => {
|
||||
done()
|
||||
})
|
||||
|
||||
expect(setTimeoutSpy).toBeCalledTimes(0)
|
||||
expect(setIntervalSpy).toBeCalledTimes(1)
|
||||
expect(setIntervalSpy.mock.calls[0][1]).toEqual(waitTime)
|
||||
expect(clearIntervalSpy).toBeCalledTimes(0)
|
||||
expect(socket.destroy).toBeCalledTimes(0)
|
||||
|
||||
jest.advanceTimersByTime(200)
|
||||
|
||||
expect(socket.destroy).toBeCalledTimes(0)
|
||||
|
||||
finishRequestMock()
|
||||
|
||||
expect(socket.destroy).toBeCalledTimes(0)
|
||||
|
||||
jest.advanceTimersByTime(waitTime)
|
||||
|
||||
expect(socket.destroy).toBeCalledTimes(1)
|
||||
|
||||
closeServerCallback!()
|
||||
})
|
||||
|
||||
it("should force close all connections after the timeout is reached", (done: Function) => {
|
||||
jest.useFakeTimers()
|
||||
|
||||
const onEventMock = jest.fn()
|
||||
const setIntervalSpy = jest.spyOn(global, "setInterval")
|
||||
const setTimeoutSpy = jest.spyOn(global, "setTimeout")
|
||||
const clearIntervalSpy = jest.spyOn(global, "clearInterval")
|
||||
|
||||
const waitTime = 300
|
||||
let closeServerCallback: Function
|
||||
const server = GracefulShutdownServer.create(
|
||||
{
|
||||
close: (callback) => {
|
||||
closeServerCallback = callback
|
||||
},
|
||||
on: onEventMock,
|
||||
} as any,
|
||||
waitTime
|
||||
)
|
||||
|
||||
const requestMock = onEventMock.mock.calls[2][1]
|
||||
const connectEvent: (socket) => any = onEventMock.mock.calls[0][1]
|
||||
|
||||
expect(typeof requestMock).toEqual("function")
|
||||
|
||||
const socket = { on: jest.fn(), destroy: jest.fn() }
|
||||
const req = { socket, on: jest.fn() }
|
||||
const res = { on: jest.fn() }
|
||||
connectEvent(socket)
|
||||
|
||||
requestMock(req, res) // pending request
|
||||
|
||||
const forceTimeout = 600
|
||||
server.shutdown(forceTimeout).then(() => {
|
||||
done()
|
||||
})
|
||||
|
||||
expect(setTimeoutSpy).toBeCalledTimes(1)
|
||||
expect(setTimeoutSpy.mock.calls[0][1]).toEqual(forceTimeout)
|
||||
expect(setIntervalSpy).toBeCalledTimes(1)
|
||||
expect(setIntervalSpy.mock.calls[0][1]).toEqual(waitTime)
|
||||
expect(clearIntervalSpy).toBeCalledTimes(0)
|
||||
expect(socket.destroy).toBeCalledTimes(0)
|
||||
|
||||
jest.advanceTimersByTime(waitTime)
|
||||
expect(socket.destroy).toBeCalledTimes(0)
|
||||
|
||||
jest.advanceTimersByTime(forceTimeout)
|
||||
expect(socket.destroy).toBeCalledTimes(1)
|
||||
|
||||
closeServerCallback!()
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
export enum PostgresError {
|
||||
DUPLICATE_ERROR = "23505",
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
stringToSelectRelationObject,
|
||||
} from "@medusajs/utils"
|
||||
import { pick } from "lodash"
|
||||
import { MedusaError, isDefined } from "medusa-core-utils"
|
||||
import { MedusaError, isDefined } from "@medusajs/utils"
|
||||
import { RequestQueryFields } from "@medusajs/types"
|
||||
import { FindConfig, QueryConfig } from "../types/common"
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Server } from "http"
|
||||
import { Socket } from "net"
|
||||
import Timeout = NodeJS.Timeout
|
||||
|
||||
interface SocketState extends Socket {
|
||||
_idle: boolean
|
||||
_connectionId: number
|
||||
}
|
||||
|
||||
export abstract class GracefulShutdownServer {
|
||||
public isShuttingDown: boolean
|
||||
public abstract shutdown(timeout?: number): Promise<void>
|
||||
public static create<T extends Server>(
|
||||
originalServer: T,
|
||||
waitingResponseTime: number = 200
|
||||
): T & GracefulShutdownServer {
|
||||
let connectionId = 0
|
||||
let shutdownPromise: Promise<void>
|
||||
|
||||
const allSockets: { [id: number]: SocketState } = {}
|
||||
|
||||
const server = originalServer as T & GracefulShutdownServer
|
||||
server.isShuttingDown = false
|
||||
server.shutdown = async (timeout: number = 0): Promise<void> => {
|
||||
if (server.isShuttingDown) {
|
||||
return shutdownPromise
|
||||
}
|
||||
|
||||
server.isShuttingDown = true
|
||||
|
||||
shutdownPromise = new Promise((ok, nok) => {
|
||||
let forceQuit = false
|
||||
let cleanInterval: Timeout
|
||||
|
||||
try {
|
||||
// stop accepting new incoming connections
|
||||
server.close(() => {
|
||||
clearInterval(cleanInterval)
|
||||
ok()
|
||||
})
|
||||
|
||||
if (+timeout > 0) {
|
||||
setTimeout(() => {
|
||||
forceQuit = true
|
||||
}, timeout).unref()
|
||||
}
|
||||
|
||||
cleanInterval = setInterval(() => {
|
||||
if (!Object.keys(allSockets).length) {
|
||||
clearInterval(cleanInterval)
|
||||
}
|
||||
|
||||
for (const key of Object.keys(allSockets)) {
|
||||
const socketId = +key
|
||||
if (forceQuit || allSockets[socketId]._idle) {
|
||||
allSockets[socketId].destroy()
|
||||
delete allSockets[socketId]
|
||||
}
|
||||
}
|
||||
}, waitingResponseTime)
|
||||
} catch (error) {
|
||||
clearInterval(cleanInterval!)
|
||||
return nok(error)
|
||||
}
|
||||
})
|
||||
|
||||
return shutdownPromise
|
||||
}
|
||||
|
||||
const onConnect = (originalSocket) => {
|
||||
connectionId++
|
||||
const socket = originalSocket as SocketState
|
||||
socket._idle = true
|
||||
socket._connectionId = connectionId
|
||||
allSockets[connectionId] = socket
|
||||
|
||||
socket.on("close", () => {
|
||||
delete allSockets[socket._connectionId]
|
||||
})
|
||||
}
|
||||
|
||||
server.on("connection", onConnect)
|
||||
server.on("secureConnection", onConnect)
|
||||
|
||||
server.on("request", (req, res) => {
|
||||
const customSocket = req.socket as SocketState
|
||||
customSocket._idle = false
|
||||
|
||||
res.on("finish", () => {
|
||||
customSocket._idle = true
|
||||
})
|
||||
})
|
||||
|
||||
return server
|
||||
}
|
||||
}
|
||||
@@ -3,3 +3,4 @@ export * from "./exception-formatter"
|
||||
export * from "./middlewares"
|
||||
export * from "./omit-deep"
|
||||
export * from "./remove-undefined-properties"
|
||||
export * from "./graceful-shutdown-server"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextFunction, Request, Response } from "express"
|
||||
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
import { Logger } from "../../types/global"
|
||||
import { formatException } from "../../utils"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isDefined } from "medusa-core-utils"
|
||||
import { isDefined } from "@medusajs/utils"
|
||||
|
||||
export function removeUndefinedProperties<T extends object>(inputObj: T): T {
|
||||
const removeProperties = (obj: T) => {
|
||||
|
||||
Reference in New Issue
Block a user