diff --git a/.eslintignore b/.eslintignore index 66fa041a02a90..ed0ba98e31562 100644 --- a/.eslintignore +++ b/.eslintignore @@ -33,6 +33,7 @@ packages/* !packages/workflow-engine-inmemory !packages/fulfillment !packages/fulfillment-manual +!packages/locking-redis !packages/index !packages/framework diff --git a/.eslintrc.js b/.eslintrc.js index f960018932f77..02c2ee5f161b3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -134,6 +134,7 @@ module.exports = { "./packages/modules/providers/file-s3/tsconfig.spec.json", "./packages/modules/providers/fulfillment-manual/tsconfig.spec.json", "./packages/modules/providers/payment-stripe/tsconfig.spec.json", + "./packages/modules/providers/locking-redis/tsconfig.spec.json", "./packages/framework/tsconfig.json", ], diff --git a/integration-tests/modules/medusa-config.js b/integration-tests/modules/medusa-config.js index 08f890690bd25..df3b3e6ae383a 100644 --- a/integration-tests/modules/medusa-config.js +++ b/integration-tests/modules/medusa-config.js @@ -62,6 +62,7 @@ module.exports = { resolve: "@medusajs/cache-inmemory", options: { ttl: 0 }, // Cache disabled }, + [Modules.LOCKING]: true, [Modules.STOCK_LOCATION]: { resolve: "@medusajs/stock-location-next", options: {}, diff --git a/packages/admin/admin-bundler/src/types.ts b/packages/admin/admin-bundler/src/types.ts index ca56df216f035..491307037e484 100644 --- a/packages/admin/admin-bundler/src/types.ts +++ b/packages/admin/admin-bundler/src/types.ts @@ -1,6 +1,7 @@ import { AdminOptions } from "@medusajs/types" -export type BundlerOptions = Required> & +export type BundlerOptions = Required> & Pick & { + outDir: string sources?: string[] } diff --git a/packages/admin/admin-vite-plugin/package.json b/packages/admin/admin-vite-plugin/package.json index da5072fc2e832..8a1b3c332104f 100644 --- a/packages/admin/admin-vite-plugin/package.json +++ b/packages/admin/admin-vite-plugin/package.json @@ -22,14 +22,17 @@ ], "scripts": { "build": "tsup", - "watch": "tsup --watch" + "watch": "tsup --watch", + "test": "vitest --run", + "test:watch": "vitest" }, "devDependencies": { "@babel/types": "7.25.6", "@types/node": "^20.10.4", "tsup": "8.0.1", "typescript": "5.3.3", - "vite": "^5.2.11" + "vite": "^5.2.11", + "vitest": "^2.1.3" }, "peerDependencies": { "vite": "^5.0.0" diff --git a/packages/admin/admin-vite-plugin/src/custom-fields/generate-custom-field-displays.ts b/packages/admin/admin-vite-plugin/src/custom-fields/generate-custom-field-displays.ts index 2ac717b118fe2..d18ce42cb32c8 100644 --- a/packages/admin/admin-vite-plugin/src/custom-fields/generate-custom-field-displays.ts +++ b/packages/admin/admin-vite-plugin/src/custom-fields/generate-custom-field-displays.ts @@ -20,7 +20,7 @@ import { traverse, } from "../babel" import { logger } from "../logger" -import { crawl, getParserOptions } from "../utils" +import { crawl, getParserOptions, normalizePath } from "../utils" import { getConfigArgument, getModel, validateLink } from "./helpers" type CustomFieldDisplay = { @@ -288,5 +288,6 @@ function generateCustomFieldConfigName(index: number): string { } function generateImport(file: string, index: number): string { - return `import ${generateCustomFieldConfigName(index)} from "${file}"` + const path = normalizePath(file) + return `import ${generateCustomFieldConfigName(index)} from "${path}"` } diff --git a/packages/admin/admin-vite-plugin/src/custom-fields/generate-custom-field-forms.ts b/packages/admin/admin-vite-plugin/src/custom-fields/generate-custom-field-forms.ts index 2f11ee3ff94c3..1bb2c3d967bf2 100644 --- a/packages/admin/admin-vite-plugin/src/custom-fields/generate-custom-field-forms.ts +++ b/packages/admin/admin-vite-plugin/src/custom-fields/generate-custom-field-forms.ts @@ -29,7 +29,7 @@ import { traverse, } from "../babel" import { logger } from "../logger" -import { crawl, getParserOptions } from "../utils" +import { crawl, getParserOptions, normalizePath } from "../utils" import { getConfigArgument, getModel, validateLink } from "./helpers" type CustomFieldConfigField = { @@ -263,7 +263,8 @@ function generateCustomFieldConfigName(index: number): string { } function generateImport(file: string, index: number): string { - return `import ${generateCustomFieldConfigName(index)} from "${file}"` + const path = normalizePath(file) + return `import ${generateCustomFieldConfigName(index)} from "${path}"` } function getForms( diff --git a/packages/admin/admin-vite-plugin/src/custom-fields/generate-custom-field-links.ts b/packages/admin/admin-vite-plugin/src/custom-fields/generate-custom-field-links.ts index ac4e4fd7b6906..9c363b36f6e83 100644 --- a/packages/admin/admin-vite-plugin/src/custom-fields/generate-custom-field-links.ts +++ b/packages/admin/admin-vite-plugin/src/custom-fields/generate-custom-field-links.ts @@ -12,7 +12,7 @@ import { traverse, } from "../babel" import { logger } from "../logger" -import { crawl, getParserOptions } from "../utils" +import { crawl, getParserOptions, normalizePath } from "../utils" import { getConfigArgument, getModel } from "./helpers" type ParsedCustomFieldLink = { @@ -138,7 +138,8 @@ function generateCustomFieldConfigName(index: number): string { } function generateImport(file: string, index: number): string { - return `import ${generateCustomFieldConfigName(index)} from "${file}"` + const path = normalizePath(file) + return `import ${generateCustomFieldConfigName(index)} from "${path}"` } function getLink( diff --git a/packages/admin/admin-vite-plugin/src/routes/__tests__/generate-menu-items.spec.ts b/packages/admin/admin-vite-plugin/src/routes/__tests__/generate-menu-items.spec.ts new file mode 100644 index 0000000000000..cb8ccd98eaa39 --- /dev/null +++ b/packages/admin/admin-vite-plugin/src/routes/__tests__/generate-menu-items.spec.ts @@ -0,0 +1,113 @@ +import { describe, expect, it, vi } from "vitest" + +import fs from "fs/promises" +import * as utils from "../../utils" +import { generateMenuItems } from "../generate-menu-items" + +vi.mock("../../utils", async () => { + const actual = await vi.importActual("../../utils") + return { + ...actual, + crawl: vi.fn(), + } +}) + +vi.mock("fs/promises", () => ({ + default: { + readFile: vi.fn(), + }, +})) + +const mockFileContents = [ + ` + import { defineRouteConfig } from "@medusajs/admin-sdk" + + const Page = () => { + return
Page 1
+ } + + export const config = defineRouteConfig({ + label: "Page 1", + icon: "icon1", + }) + + export default Page + `, + ` + import { defineRouteConfig } from "@medusajs/admin-sdk" + + const Page = () => { + return
Page 2
+ } + + export const config = defineRouteConfig({ + label: "Page 2", + }) + + export default Page + `, +] + +const expectedMenuItems = ` + menuItems: [ + { + label: RouteConfig0.label, + icon: RouteConfig0.icon, + path: "/one", + }, + { + label: RouteConfig1.label, + icon: undefined, + path: "/two", + } + ] + ` + +describe("generateMenuItems", () => { + it("should generate menu items", async () => { + const mockFiles = [ + "Users/user/medusa/src/admin/routes/one/page.tsx", + "Users/user/medusa/src/admin/routes/two/page.tsx", + ] + vi.mocked(utils.crawl).mockResolvedValue(mockFiles) + + vi.mocked(fs.readFile).mockImplementation(async (file) => + Promise.resolve(mockFileContents[mockFiles.indexOf(file as string)]) + ) + + const result = await generateMenuItems( + new Set(["Users/user/medusa/src/admin"]) + ) + + expect(result.imports).toEqual([ + `import { config as RouteConfig0 } from "Users/user/medusa/src/admin/routes/one/page.tsx"`, + `import { config as RouteConfig1 } from "Users/user/medusa/src/admin/routes/two/page.tsx"`, + ]) + expect(utils.normalizeString(result.code)).toEqual( + utils.normalizeString(expectedMenuItems) + ) + }) + + it("should handle windows paths", async () => { + // Setup mocks + const mockFiles = [ + "C:\\medusa\\src\\admin\\routes\\one\\page.tsx", + "C:\\medusa\\src\\admin\\routes\\two\\page.tsx", + ] + vi.mocked(utils.crawl).mockResolvedValue(mockFiles) + + vi.mocked(fs.readFile).mockImplementation(async (file) => + Promise.resolve(mockFileContents[mockFiles.indexOf(file as string)]) + ) + + const result = await generateMenuItems(new Set(["C:\\medusa\\src\\admin"])) + + expect(result.imports).toEqual([ + `import { config as RouteConfig0 } from "C:/medusa/src/admin/routes/one/page.tsx"`, + `import { config as RouteConfig1 } from "C:/medusa/src/admin/routes/two/page.tsx"`, + ]) + expect(utils.normalizeString(result.code)).toEqual( + utils.normalizeString(expectedMenuItems) + ) + }) +}) diff --git a/packages/admin/admin-vite-plugin/src/routes/__tests__/generate-routes.spec.ts b/packages/admin/admin-vite-plugin/src/routes/__tests__/generate-routes.spec.ts new file mode 100644 index 0000000000000..6ef9e1f1fe75e --- /dev/null +++ b/packages/admin/admin-vite-plugin/src/routes/__tests__/generate-routes.spec.ts @@ -0,0 +1,144 @@ +import { describe, expect, it, vi } from "vitest" + +import { Stats } from "fs" +import fs from "fs/promises" +import * as utils from "../../utils" +import { generateRoutes } from "../generate-routes" + +// Mock the dependencies +vi.mock("../../utils", async () => { + const actual = await vi.importActual("../../utils") + return { + ...actual, + crawl: vi.fn(), + } +}) + +vi.mock("fs/promises", () => ({ + default: { + readFile: vi.fn(), + stat: vi.fn(), + }, +})) + +const mockFileContents = [ + ` + import { defineRouteConfig } from "@medusajs/admin-sdk" + + const Page = () => { + return
Page 1
+ } + + export const config = defineRouteConfig({ + label: "Page 1", + icon: "icon1", + }) + + export default Page + `, + ` + import { defineRouteConfig } from "@medusajs/admin-sdk" + + const Page = () => { + return
Page 2
+ } + + export const config = defineRouteConfig({ + label: "Page 2", + }) + + export default Page + `, +] + +const expectedRoutesWithoutLoaders = ` + routes: [ + { + Component: RouteComponent0, + loader: undefined, + path: "/one", + }, + { + Component: RouteComponent1, + loader: undefined, + path: "/two", + } + ] +` + +const expectedRoutesWithLoaders = ` + routes: [ + { + Component: RouteComponent0, + loader: RouteLoader0, + path: "/one", + }, + { + Component: RouteComponent1, + loader: RouteLoader1, + path: "/two", + } + ] +` + +describe("generateRoutes", () => { + it("should generate routes", async () => { + const mockFiles = [ + "Users/user/medusa/src/admin/routes/one/page.tsx", + "Users/user/medusa/src/admin/routes/two/page.tsx", + ] + vi.mocked(utils.crawl).mockResolvedValue(mockFiles) + + vi.mocked(fs.readFile).mockImplementation(async (file) => + Promise.resolve(mockFileContents[mockFiles.indexOf(file as string)]) + ) + + vi.mocked(fs.stat).mockRejectedValue(new Error("File not found")) + + const result = await generateRoutes( + new Set(["Users/user/medusa/src/admin"]) + ) + expect(utils.normalizeString(result.code)).toEqual( + utils.normalizeString(expectedRoutesWithoutLoaders) + ) + }) + it("should generate routes with loaders", async () => { + const mockFiles = [ + "Users/user/medusa/src/admin/routes/one/page.tsx", + "Users/user/medusa/src/admin/routes/two/page.tsx", + ] + vi.mocked(utils.crawl).mockResolvedValue(mockFiles) + + vi.mocked(fs.readFile).mockImplementation(async (file) => + Promise.resolve(mockFileContents[mockFiles.indexOf(file as string)]) + ) + + vi.mocked(fs.stat).mockResolvedValue({} as Stats) // We just want to mock that the check passes + + const result = await generateRoutes( + new Set(["Users/user/medusa/src/admin"]) + ) + expect(utils.normalizeString(result.code)).toEqual( + utils.normalizeString(expectedRoutesWithLoaders) + ) + }) + it("should handle windows paths", async () => { + const mockFiles = [ + "C:\\medusa\\src\\admin\\routes\\one\\page.tsx", + "C:\\medusa\\src\\admin\\routes\\two\\page.tsx", + ] + vi.mocked(utils.crawl).mockResolvedValue(mockFiles) + + vi.mocked(fs.readFile).mockImplementation(async (file) => + Promise.resolve(mockFileContents[mockFiles.indexOf(file as string)]) + ) + + vi.mocked(fs.stat).mockRejectedValue(new Error("File not found")) + + const result = await generateRoutes(new Set(["C:\\medusa\\src\\admin"])) + + expect(utils.normalizeString(result.code)).toEqual( + utils.normalizeString(expectedRoutesWithoutLoaders) + ) + }) +}) diff --git a/packages/admin/admin-vite-plugin/src/routes/generate-menu-items.ts b/packages/admin/admin-vite-plugin/src/routes/generate-menu-items.ts index 7c738903462c4..19ef309810501 100644 --- a/packages/admin/admin-vite-plugin/src/routes/generate-menu-items.ts +++ b/packages/admin/admin-vite-plugin/src/routes/generate-menu-items.ts @@ -2,7 +2,12 @@ import fs from "fs/promises" import { outdent } from "outdent" import { isIdentifier, isObjectProperty, parse, traverse } from "../babel" import { logger } from "../logger" -import { crawl, getConfigObjectProperties, getParserOptions } from "../utils" +import { + crawl, + getConfigObjectProperties, + getParserOptions, + normalizePath, +} from "../utils" import { getRoute } from "./helpers" type MenuItem = { @@ -90,7 +95,8 @@ async function parseFile( } function generateImport(file: string, index: number): string { - return `import { config as ${generateRouteConfigName(index)} } from "${file}"` + const path = normalizePath(file) + return `import { config as ${generateRouteConfigName(index)} } from "${path}"` } function generateMenuItem( diff --git a/packages/admin/admin-vite-plugin/src/utils.ts b/packages/admin/admin-vite-plugin/src/utils.ts index 0522ef49a00f0..1b094db0fb0e2 100644 --- a/packages/admin/admin-vite-plugin/src/utils.ts +++ b/packages/admin/admin-vite-plugin/src/utils.ts @@ -17,7 +17,7 @@ import { } from "./babel" export function normalizePath(file: string) { - return path.normalize(file).split(path.sep).join("/") + return path.normalize(file).replace(/\\/g, "/") } /** @@ -145,3 +145,11 @@ export function isFileInAdminSubdirectory( const normalizedPath = normalizePath(file) return normalizedPath.includes(`/src/admin/${subdirectory}/`) } + +/** + * Test util to normalize strings, so they can be compared without taking + * whitespace into account. + */ +export function normalizeString(str: string): string { + return str.replace(/\s+/g, " ").trim() +} diff --git a/packages/admin/admin-vite-plugin/src/widgets/__tests__/generate-widgets.spec.ts b/packages/admin/admin-vite-plugin/src/widgets/__tests__/generate-widgets.spec.ts new file mode 100644 index 0000000000000..2f31ca82b4f7e --- /dev/null +++ b/packages/admin/admin-vite-plugin/src/widgets/__tests__/generate-widgets.spec.ts @@ -0,0 +1,83 @@ +import { vi } from "vitest" + +import fs from "fs/promises" +import * as utils from "../../utils" +import { generateWidgets } from "../generate-widgets" + +vi.mock("../../utils", async () => { + const actual = await vi.importActual("../../utils") + return { + ...actual, + crawl: vi.fn(), + } +}) + +vi.mock("fs/promises", () => ({ + default: { + readFile: vi.fn(), + }, +})) + +const mockFileContents = [ + ` + import { defineWidgetConfig } from "@medusajs/admin-sdk" + + const Widget = () => { + return
Widget 1
+ } + + export const config = defineWidgetConfig({ + zone: "product.details.after", + }) + + export default Widget +`, +] + +const expectedWidgets = ` + widgets: [ + { + Component: WidgetComponent0, + zone: ["product.details.after"] + } + ] +` + +describe("generateWidgets", () => { + it("should generate widgets", async () => { + const mockFiles = ["Users/user/medusa/src/admin/widgets/widget.tsx"] + vi.mocked(utils.crawl).mockResolvedValue(mockFiles) + + vi.mocked(fs.readFile).mockImplementation(async (file) => + Promise.resolve(mockFileContents[mockFiles.indexOf(file as string)]) + ) + + const result = await generateWidgets( + new Set(["Users/user/medusa/src/admin"]) + ) + + expect(result.imports).toEqual([ + `import WidgetComponent0, { config as WidgetConfig0 } from "Users/user/medusa/src/admin/widgets/widget.tsx"`, + ]) + expect(utils.normalizeString(result.code)).toEqual( + utils.normalizeString(expectedWidgets) + ) + }) + it("should handle windows paths", async () => { + const mockFiles = ["C:\\medusa\\src\\admin\\widgets\\widget.tsx"] + vi.mocked(utils.crawl).mockResolvedValue(mockFiles) + + vi.mocked(fs.readFile).mockImplementation(async (file) => + Promise.resolve(mockFileContents[mockFiles.indexOf(file as string)]) + ) + + const result = await generateWidgets(new Set(["C:\\medusa\\src\\admin"])) + + expect(result.imports).toEqual([ + `import WidgetComponent0, { config as WidgetConfig0 } from "C:/medusa/src/admin/widgets/widget.tsx"`, + ]) + expect(utils.normalizeString(result.code)).toEqual( + utils.normalizeString(expectedWidgets) + ) + }) +}) diff --git a/packages/admin/admin-vite-plugin/src/widgets/generate-widgets.ts b/packages/admin/admin-vite-plugin/src/widgets/generate-widgets.ts index 5a96dbf973e57..af5960458316f 100644 --- a/packages/admin/admin-vite-plugin/src/widgets/generate-widgets.ts +++ b/packages/admin/admin-vite-plugin/src/widgets/generate-widgets.ts @@ -16,6 +16,7 @@ import { getConfigObjectProperties, getParserOptions, hasDefaultExport, + normalizePath, } from "../utils" import { getWidgetFilesFromSources } from "./helpers" @@ -135,9 +136,10 @@ function generateWidgetConfigName(index: number): string { } function generateImport(file: string, index: number): string { + const path = normalizePath(file) return `import ${generateWidgetComponentName( index - )}, { config as ${generateWidgetConfigName(index)} } from "${file}"` + )}, { config as ${generateWidgetConfigName(index)} } from "${path}"` } function generateWidget(zone: InjectionZone[], index: number): WidgetConfig { diff --git a/packages/admin/admin-vite-plugin/tsconfig.build.json b/packages/admin/admin-vite-plugin/tsconfig.build.json new file mode 100644 index 0000000000000..d853b83a4105b --- /dev/null +++ b/packages/admin/admin-vite-plugin/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["**/*.spec.ts", "vitest.config.ts", "tsup.config.ts"] +} diff --git a/packages/admin/admin-vite-plugin/tsconfig.json b/packages/admin/admin-vite-plugin/tsconfig.json index aa9f8ff0f3b68..ec7fcfec0c22a 100644 --- a/packages/admin/admin-vite-plugin/tsconfig.json +++ b/packages/admin/admin-vite-plugin/tsconfig.json @@ -18,5 +18,5 @@ "@babel/types": ["../../../node_modules/@babel/types"] } }, - "include": ["src"] + "include": ["src", "vitest.config.ts", "tsup.config.ts"] } diff --git a/packages/admin/admin-vite-plugin/tsup.config.cjs b/packages/admin/admin-vite-plugin/tsup.config.ts similarity index 81% rename from packages/admin/admin-vite-plugin/tsup.config.cjs rename to packages/admin/admin-vite-plugin/tsup.config.ts index f54ede878e0d8..adc4f96458ccc 100644 --- a/packages/admin/admin-vite-plugin/tsup.config.cjs +++ b/packages/admin/admin-vite-plugin/tsup.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from "tsup" export default defineConfig({ entry: ["./src/index.ts"], + tsconfig: "tsconfig.build.json", format: ["cjs", "esm"], dts: true, clean: true, diff --git a/packages/admin/admin-vite-plugin/vitest.config.ts b/packages/admin/admin-vite-plugin/vitest.config.ts new file mode 100644 index 0000000000000..b93b2bbe7688c --- /dev/null +++ b/packages/admin/admin-vite-plugin/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config" + +export default defineConfig({ + test: { + globals: true, + environment: "node", + include: ["**/*.spec.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], + }, +}) diff --git a/packages/cli/medusa-cli/src/create-cli.ts b/packages/cli/medusa-cli/src/create-cli.ts index 653474caa8c53..f817e618c7463 100644 --- a/packages/cli/medusa-cli/src/create-cli.ts +++ b/packages/cli/medusa-cli/src/create-cli.ts @@ -354,9 +354,15 @@ function buildLocalCommands(cli, isLocalProject) { ), }) .command({ - command: `build`, - desc: `Build your project.`, - builder: (_) => _, + command: "build", + desc: "Build your project.", + builder: (_) => + _.option("admin-only", { + default: false, + type: "boolean", + describe: + "Only build the admin to serve it separately (outDir .medusa/admin)", + }), handler: handlerP( getCommandHandler(`build`, (args, cmd) => { process.env.NODE_ENV = process.env.NODE_ENV || `development` diff --git a/packages/cli/medusa-dev-cli/src/index.js b/packages/cli/medusa-dev-cli/src/index.js index acc33f0aa7ee5..d551a6a091f8f 100644 --- a/packages/cli/medusa-dev-cli/src/index.js +++ b/packages/cli/medusa-dev-cli/src/index.js @@ -1,5 +1,11 @@ #!/usr/bin/env node +try { + require("ts-node").register({}) + require("tsconfig-paths").register({}) +} catch {} +require("dotenv").config() + const Configstore = require(`configstore`) const pkg = require(`../package.json`) const _ = require(`lodash`) diff --git a/packages/core/core-flows/src/cart/steps/reserve-inventory.ts b/packages/core/core-flows/src/cart/steps/reserve-inventory.ts index 66f4645d27c7c..b749933467a23 100644 --- a/packages/core/core-flows/src/cart/steps/reserve-inventory.ts +++ b/packages/core/core-flows/src/cart/steps/reserve-inventory.ts @@ -1,6 +1,5 @@ -import { IInventoryService } from "@medusajs/framework/types" import { MathBN, Modules } from "@medusajs/framework/utils" -import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk" +import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" import { BigNumberInput } from "@medusajs/types" export interface ReserveVariantInventoryStepInput { @@ -22,34 +21,45 @@ export const reserveInventoryStepId = "reserve-inventory-step" export const reserveInventoryStep = createStep( reserveInventoryStepId, async (data: ReserveVariantInventoryStepInput, { container }) => { - const inventoryService = container.resolve( - Modules.INVENTORY - ) + const inventoryService = container.resolve(Modules.INVENTORY) - const items = data.items.map((item) => ({ - line_item_id: item.id, - inventory_item_id: item.inventory_item_id, - quantity: MathBN.mult(item.required_quantity, item.quantity), - allow_backorder: item.allow_backorder, - location_id: item.location_ids[0], - })) + const locking = container.resolve(Modules.LOCKING) - const reservations = await inventoryService.createReservationItems(items) + const inventoryItemIds: string[] = [] + + const items = data.items.map((item) => { + inventoryItemIds.push(item.inventory_item_id) + + return { + line_item_id: item.id, + inventory_item_id: item.inventory_item_id, + quantity: MathBN.mult(item.required_quantity, item.quantity), + allow_backorder: item.allow_backorder, + location_id: item.location_ids[0], + } + }) + + const reservations = await locking.execute(inventoryItemIds, async () => { + return await inventoryService.createReservationItems(items) + }) return new StepResponse(reservations, { reservations: reservations.map((r) => r.id), + inventoryItemIds, }) }, async (data, { container }) => { - if (!data) { + if (!data?.reservations?.length) { return } - const inventoryService = container.resolve( - Modules.INVENTORY - ) + const inventoryService = container.resolve(Modules.INVENTORY) + const locking = container.resolve(Modules.LOCKING) - await inventoryService.deleteReservationItems(data.reservations) + const inventoryItemIds = data.inventoryItemIds + await locking.execute(inventoryItemIds, async () => { + await inventoryService.deleteReservationItems(data.reservations) + }) return new StepResponse() } diff --git a/packages/core/core-flows/src/reservation/steps/create-reservations.ts b/packages/core/core-flows/src/reservation/steps/create-reservations.ts index 2c01d0ce696d0..d186798b2383a 100644 --- a/packages/core/core-flows/src/reservation/steps/create-reservations.ts +++ b/packages/core/core-flows/src/reservation/steps/create-reservations.ts @@ -1,5 +1,5 @@ -import { IInventoryService, InventoryTypes } from "@medusajs/framework/types" -import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk" +import { InventoryTypes } from "@medusajs/framework/types" +import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" import { Modules } from "@medusajs/framework/utils" @@ -10,22 +10,31 @@ export const createReservationsStepId = "create-reservations-step" export const createReservationsStep = createStep( createReservationsStepId, async (data: InventoryTypes.CreateReservationItemInput[], { container }) => { - const service = container.resolve(Modules.INVENTORY) + const service = container.resolve(Modules.INVENTORY) + const locking = container.resolve(Modules.LOCKING) - const created = await service.createReservationItems(data) + const inventoryItemIds = data.map((item) => item.inventory_item_id) - return new StepResponse( - created, - created.map((reservation) => reservation.id) - ) + const created = await locking.execute(inventoryItemIds, async () => { + return await service.createReservationItems(data) + }) + + return new StepResponse(created, { + reservations: created.map((reservation) => reservation.id), + inventoryItemIds: inventoryItemIds, + }) }, - async (createdIds, { container }) => { - if (!createdIds?.length) { + async (data, { container }) => { + if (!data?.reservations?.length) { return } - const service = container.resolve(Modules.INVENTORY) + const service = container.resolve(Modules.INVENTORY) + const locking = container.resolve(Modules.LOCKING) - await service.deleteReservationItems(createdIds) + const inventoryItemIds = data.inventoryItemIds + await locking.execute(inventoryItemIds, async () => { + await service.deleteReservationItems(data.reservations) + }) } ) diff --git a/packages/core/framework/src/config/__tests__/index.spec.ts b/packages/core/framework/src/config/__tests__/index.spec.ts index b38b5556adb71..4020c9b9ddc87 100644 --- a/packages/core/framework/src/config/__tests__/index.spec.ts +++ b/packages/core/framework/src/config/__tests__/index.spec.ts @@ -13,14 +13,14 @@ describe("configLoader", () => { expect(configModule).toBeUndefined() - configLoader(entryDirectory, "medusa-config") + await configLoader(entryDirectory, "medusa-config") configModule = container.resolve(ContainerRegistrationKeys.CONFIG_MODULE) expect(configModule).toBeDefined() expect(configModule.projectConfig.databaseName).toBeUndefined() - configLoader(entryDirectory, "medusa-config-2") + await configLoader(entryDirectory, "medusa-config-2") configModule = container.resolve(ContainerRegistrationKeys.CONFIG_MODULE) @@ -30,7 +30,7 @@ describe("configLoader", () => { process.env.MEDUSA_WORKER_MODE = "worker" - configLoader(entryDirectory, "medusa-config-2") + await configLoader(entryDirectory, "medusa-config-2") configModule = container.resolve(ContainerRegistrationKeys.CONFIG_MODULE) diff --git a/packages/core/framework/src/config/loader.ts b/packages/core/framework/src/config/loader.ts index 698b64a761534..18c555ff8407d 100644 --- a/packages/core/framework/src/config/loader.ts +++ b/packages/core/framework/src/config/loader.ts @@ -26,11 +26,14 @@ container.register( * @param entryDirectory The directory to find the config file from * @param configFileName The name of the config file to search for in the entry directory */ -export function configLoader( +export async function configLoader( entryDirectory: string, configFileName: string -): ConfigModule { - const config = getConfigFile(entryDirectory, configFileName) +): Promise { + const config = await getConfigFile( + entryDirectory, + configFileName + ) if (config.error) { handleConfigError(config.error) diff --git a/packages/core/modules-sdk/src/loaders/module-loader.ts b/packages/core/modules-sdk/src/loaders/module-loader.ts index 05a9dd207c339..c0ec8b24f1222 100644 --- a/packages/core/modules-sdk/src/loaders/module-loader.ts +++ b/packages/core/modules-sdk/src/loaders/module-loader.ts @@ -82,11 +82,11 @@ async function loadModule( return } - return await loadInternalModule( + return await loadInternalModule({ container, resolution, logger, migrationOnly, - loaderOnly - ) + loaderOnly, + }) } diff --git a/packages/core/modules-sdk/src/loaders/register-modules.ts b/packages/core/modules-sdk/src/loaders/register-modules.ts index b2162ec2df48a..43379e573434c 100644 --- a/packages/core/modules-sdk/src/loaders/register-modules.ts +++ b/packages/core/modules-sdk/src/loaders/register-modules.ts @@ -67,7 +67,9 @@ function getCustomModuleResolution( const originalPath = normalizeImportPathWithSource( (isString(moduleConfig) ? moduleConfig : moduleConfig.resolve) as string ) - const resolutionPath = require.resolve(originalPath) + const resolutionPath = require.resolve(originalPath, { + paths: [process.cwd()], + }) const conf = isObject(moduleConfig) ? moduleConfig @@ -142,7 +144,9 @@ function getInternalModuleResolution( const originalPath = normalizeImportPathWithSource( (isString(moduleConfig) ? moduleConfig : moduleConfig.resolve) as string ) - resolutionPath = require.resolve(originalPath) + resolutionPath = require.resolve(originalPath, { + paths: [process.cwd()], + }) } const moduleDeclaration = isObj ? moduleConfig : {} diff --git a/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/index.ts b/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/index.ts new file mode 100644 index 0000000000000..3adb26aca99e5 --- /dev/null +++ b/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/index.ts @@ -0,0 +1,11 @@ +import { ModuleExports } from "@medusajs/types" +import { ModuleService } from "./services/module-service" +import { Module } from "@medusajs/utils" + +const moduleExports: ModuleExports = { + service: ModuleService, +} + +export * from "./services/module-service" + +export default Module("module-with-providers", moduleExports) diff --git a/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/provider-1/index.ts b/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/provider-1/index.ts new file mode 100644 index 0000000000000..4775c124aaa2f --- /dev/null +++ b/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/provider-1/index.ts @@ -0,0 +1,8 @@ +import { ModuleProviderService } from "./services/provider-service" +import { ModuleProvider } from "@medusajs/utils" + +export * from "./services/provider-service" + +export default ModuleProvider("provider-1", { + services: [ModuleProviderService], +}) diff --git a/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/provider-1/services/provider-service.ts b/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/provider-1/services/provider-service.ts new file mode 100644 index 0000000000000..328deec074cb5 --- /dev/null +++ b/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/provider-1/services/provider-service.ts @@ -0,0 +1,3 @@ +export class ModuleProviderService { + static identifier = "provider-1" +} diff --git a/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/provider-2/index.ts b/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/provider-2/index.ts new file mode 100644 index 0000000000000..209d35ace64b6 --- /dev/null +++ b/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/provider-2/index.ts @@ -0,0 +1,8 @@ +import { ModuleProvider2Service } from "./services/provider-service" +import { ModuleProvider } from "@medusajs/utils" + +export * from "./services/provider-service" + +export default ModuleProvider("provider-2", { + services: [ModuleProvider2Service], +}) diff --git a/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/provider-2/services/provider-service.ts b/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/provider-2/services/provider-service.ts new file mode 100644 index 0000000000000..7e581fe2b8690 --- /dev/null +++ b/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/provider-2/services/provider-service.ts @@ -0,0 +1 @@ +export class ModuleProvider2Service {} diff --git a/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/services/module-service.ts b/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/services/module-service.ts new file mode 100644 index 0000000000000..dab412f7fc7e3 --- /dev/null +++ b/packages/core/modules-sdk/src/loaders/utils/__fixtures__/module-with-providers/services/module-service.ts @@ -0,0 +1,9 @@ +import { InternalModuleDeclaration } from "@medusajs/types" + +export class ModuleService { + constructor( + public container: Record, + public moduleOptions: Record, + public moduleDeclaration: InternalModuleDeclaration + ) {} +} diff --git a/packages/core/modules-sdk/src/loaders/utils/__tests__/load-internal.spec.ts b/packages/core/modules-sdk/src/loaders/utils/__tests__/load-internal.spec.ts index f2bcfdd5f1f2f..cef3444c607c6 100644 --- a/packages/core/modules-sdk/src/loaders/utils/__tests__/load-internal.spec.ts +++ b/packages/core/modules-sdk/src/loaders/utils/__tests__/load-internal.spec.ts @@ -1,5 +1,5 @@ import { IModuleService, ModuleResolution } from "@medusajs/types" -import { upperCaseFirst } from "@medusajs/utils" +import { createMedusaContainer, upperCaseFirst } from "@medusajs/utils" import { join } from "path" import { ModuleWithDmlMixedWithoutJoinerConfigFixtures, @@ -7,79 +7,253 @@ import { ModuleWithJoinerConfigFixtures, ModuleWithoutJoinerConfigFixtures, } from "../__fixtures__" -import { loadResources } from "../load-internal" +import { + getProviderRegistrationKey, + loadInternalModule, + loadResources, +} from "../load-internal" +import { ModuleProviderService as ModuleServiceWithProviderProvider1 } from "../__fixtures__/module-with-providers/provider-1" +import { ModuleProvider2Service as ModuleServiceWithProviderProvider2 } from "../__fixtures__/module-with-providers/provider-2" +import { ModuleService as ModuleServiceWithProvider } from "../__fixtures__/module-with-providers" -describe("load internal - load resources", () => { - describe("when loading the module resources from a path", () => { - test("should return the correct resources and generate the correct joiner config from a mix of DML entities and mikro orm entities", async () => { - const { ModuleService, EntityModel, dmlEntity } = - ModuleWithDmlMixedWithoutJoinerConfigFixtures +describe("load internal", () => { + describe("loadResources", () => { + describe("when loading the module resources from a path", () => { + test("should return the correct resources and generate the correct joiner config from a mix of DML entities and mikro orm entities", async () => { + const { ModuleService, EntityModel, dmlEntity } = + ModuleWithDmlMixedWithoutJoinerConfigFixtures - const moduleResolution: ModuleResolution = { - resolutionPath: join( - __dirname, - "../__fixtures__/module-with-dml-mixed-without-joiner-config" - ), - definition: { - key: "module-with-dml-mixed-without-joiner-config", - label: "Module with DML mixed without joiner config", - defaultPackage: false, - defaultModuleDeclaration: { - scope: "internal", - resources: "shared", + const moduleResolution: ModuleResolution = { + resolutionPath: join( + __dirname, + "../__fixtures__/module-with-dml-mixed-without-joiner-config" + ), + definition: { + key: "module-with-dml-mixed-without-joiner-config", + label: "Module with DML mixed without joiner config", + defaultPackage: false, + defaultModuleDeclaration: { + scope: "internal", + resources: "shared", + }, }, - }, - } + } + + expect( + (ModuleService.prototype as IModuleService).__joinerConfig + ).toBeUndefined() + + const resources = await loadResources({ + moduleResolution, + discoveryPath: moduleResolution.resolutionPath as string, + }) + + expect(resources).toBeDefined() + expect(resources.services).toHaveLength(1) + expect(resources.services[0]).toEqual(ModuleService) + expect(resources.models).toHaveLength(2) + expect(resources.models).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: upperCaseFirst(dmlEntity.name) }), + expect.objectContaining({ name: upperCaseFirst(EntityModel.name) }), + ]) + ) + expect(resources.repositories).toHaveLength(0) + expect(resources.loaders).toHaveLength(2) + expect(resources.loaders).toEqual([ + expect.objectContaining({ name: "connectionLoader" }), + expect.objectContaining({ name: "containerLoader" }), + ]) + expect(resources.moduleService).toEqual(ModuleService) + + expect( + (resources.moduleService.prototype as IModuleService).__joinerConfig + ).toBeDefined() + + const generatedJoinerConfig = ( + resources.moduleService.prototype as IModuleService + ).__joinerConfig?.()! + + expect(generatedJoinerConfig).toEqual( + expect.objectContaining({ + serviceName: "module-with-dml-mixed-without-joiner-config", + primaryKeys: ["id"], + linkableKeys: { + dml_entity_id: "DmlEntity", + entity_model_id: "EntityModel", + }, + alias: [ + { + name: ["dml_entity", "dml_entities"], + entity: "DmlEntity", + args: { + methodSuffix: "DmlEntities", + }, + }, + { + name: ["entity_model", "entity_models"], + entity: "EntityModel", + args: { + methodSuffix: "EntityModels", + }, + }, + ], + }) + ) + }) + + test("should return the correct resources and generate the correct joiner config from DML entities", async () => { + const { ModuleService, entityModel, dmlEntity } = + ModuleWithDmlWithoutJoinerConfigFixtures + + const moduleResolution: ModuleResolution = { + resolutionPath: join( + __dirname, + "../__fixtures__/module-with-dml-without-joiner-config" + ), + definition: { + key: "module-with-dml-without-joiner-config", + label: "Module with DML without joiner config", + defaultPackage: false, + defaultModuleDeclaration: { + scope: "internal", + resources: "shared", + }, + }, + } + + expect( + (ModuleService.prototype as IModuleService).__joinerConfig + ).toBeUndefined() + + const resources = await loadResources({ + moduleResolution, + discoveryPath: moduleResolution.resolutionPath as string, + }) + + expect(resources).toBeDefined() + expect(resources.services).toHaveLength(1) + expect(resources.services[0]).toEqual(ModuleService) + expect(resources.models).toHaveLength(2) + expect(resources.models).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: upperCaseFirst(dmlEntity.name) }), + expect.objectContaining({ name: upperCaseFirst(entityModel.name) }), + ]) + ) + expect(resources.repositories).toHaveLength(0) + expect(resources.loaders).toHaveLength(2) + expect(resources.loaders).toEqual([ + expect.objectContaining({ name: "connectionLoader" }), + expect.objectContaining({ name: "containerLoader" }), + ]) + expect(resources.moduleService).toEqual(ModuleService) - expect( - (ModuleService.prototype as IModuleService).__joinerConfig - ).toBeUndefined() + expect( + (resources.moduleService.prototype as IModuleService).__joinerConfig + ).toBeDefined() - const resources = await loadResources({ - moduleResolution, - discoveryPath: moduleResolution.resolutionPath as string, + const generatedJoinerConfig = ( + resources.moduleService.prototype as IModuleService + ).__joinerConfig?.()! + + expect(generatedJoinerConfig).toEqual( + expect.objectContaining({ + serviceName: "module-with-dml-without-joiner-config", + primaryKeys: ["id"], + linkableKeys: { + entity_model_id: "EntityModel", + dml_entity_id: "DmlEntity", + }, + alias: [ + { + name: ["entity_model", "entity_models"], + entity: "EntityModel", + args: { + methodSuffix: "EntityModels", + }, + }, + { + name: ["dml_entity", "dml_entities"], + entity: "DmlEntity", + args: { + methodSuffix: "DmlEntities", + }, + }, + ], + }) + ) }) - expect(resources).toBeDefined() - expect(resources.services).toHaveLength(1) - expect(resources.services[0]).toEqual(ModuleService) - expect(resources.models).toHaveLength(2) - expect(resources.models).toEqual( - expect.arrayContaining([ - expect.objectContaining({ name: upperCaseFirst(dmlEntity.name) }), - expect.objectContaining({ name: upperCaseFirst(EntityModel.name) }), + test("should return the correct resources and generate the correct joiner config from mikro orm entities", async () => { + const { ModuleService, EntityModel, Entity2 } = + ModuleWithoutJoinerConfigFixtures + + const moduleResolution: ModuleResolution = { + resolutionPath: join( + __dirname, + "../__fixtures__/module-without-joiner-config" + ), + definition: { + key: "module-without-joiner-config", + label: "Module without joiner config", + defaultPackage: false, + defaultModuleDeclaration: { + scope: "internal", + resources: "shared", + }, + }, + } + + expect( + (ModuleService.prototype as IModuleService).__joinerConfig + ).toBeUndefined() + + const resources = await loadResources({ + moduleResolution, + discoveryPath: moduleResolution.resolutionPath as string, + }) + + expect(resources).toBeDefined() + expect(resources.services).toHaveLength(1) + expect(resources.services[0]).toEqual(ModuleService) + expect(resources.models).toHaveLength(2) + expect(resources.models).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: upperCaseFirst(EntityModel.name) }), + expect.objectContaining({ name: upperCaseFirst(Entity2.name) }), + ]) + ) + expect(resources.repositories).toHaveLength(0) + expect(resources.loaders).toHaveLength(2) + expect(resources.loaders).toEqual([ + expect.objectContaining({ name: "connectionLoader" }), + expect.objectContaining({ name: "containerLoader" }), ]) - ) - expect(resources.repositories).toHaveLength(0) - expect(resources.loaders).toHaveLength(2) - expect(resources.loaders).toEqual([ - expect.objectContaining({ name: "connectionLoader" }), - expect.objectContaining({ name: "containerLoader" }), - ]) - expect(resources.moduleService).toEqual(ModuleService) - - expect( - (resources.moduleService.prototype as IModuleService).__joinerConfig - ).toBeDefined() - - const generatedJoinerConfig = ( - resources.moduleService.prototype as IModuleService - ).__joinerConfig?.()! - - expect(generatedJoinerConfig).toEqual( - expect.objectContaining({ - serviceName: "module-with-dml-mixed-without-joiner-config", + expect(resources.moduleService).toEqual(ModuleService) + + expect( + (resources.moduleService.prototype as IModuleService).__joinerConfig + ).toBeDefined() + + const generatedJoinerConfig = ( + resources.moduleService.prototype as IModuleService + ).__joinerConfig?.()! + + expect(generatedJoinerConfig).toEqual({ + serviceName: "module-without-joiner-config", primaryKeys: ["id"], linkableKeys: { - dml_entity_id: "DmlEntity", + entity2_id: "Entity2", entity_model_id: "EntityModel", }, + schema: "", alias: [ { - name: ["dml_entity", "dml_entities"], - entity: "DmlEntity", + name: ["entity2", "entity2s"], + entity: "Entity2", args: { - methodSuffix: "DmlEntities", + methodSuffix: "Entity2s", }, }, { @@ -91,240 +265,181 @@ describe("load internal - load resources", () => { }, ], }) - ) - }) + }) - test("should return the correct resources and generate the correct joiner config from DML entities", async () => { - const { ModuleService, entityModel, dmlEntity } = - ModuleWithDmlWithoutJoinerConfigFixtures + test("should return the correct resources and use the given joiner config", async () => { + const { ModuleService, EntityModel, Entity2 } = + ModuleWithJoinerConfigFixtures - const moduleResolution: ModuleResolution = { - resolutionPath: join( - __dirname, - "../__fixtures__/module-with-dml-without-joiner-config" - ), - definition: { - key: "module-with-dml-without-joiner-config", - label: "Module with DML without joiner config", - defaultPackage: false, - defaultModuleDeclaration: { - scope: "internal", - resources: "shared", + const moduleResolution: ModuleResolution = { + resolutionPath: join( + __dirname, + "../__fixtures__/module-with-joiner-config" + ), + definition: { + key: "module-without-joiner-config", + label: "Module without joiner config", + defaultPackage: false, + defaultModuleDeclaration: { + scope: "internal", + resources: "shared", + }, }, - }, - } + } - expect( - (ModuleService.prototype as IModuleService).__joinerConfig - ).toBeUndefined() + expect( + (ModuleService.prototype as IModuleService).__joinerConfig + ).toBeDefined() - const resources = await loadResources({ - moduleResolution, - discoveryPath: moduleResolution.resolutionPath as string, - }) + const resources = await loadResources({ + moduleResolution, + discoveryPath: moduleResolution.resolutionPath as string, + }) - expect(resources).toBeDefined() - expect(resources.services).toHaveLength(1) - expect(resources.services[0]).toEqual(ModuleService) - expect(resources.models).toHaveLength(2) - expect(resources.models).toEqual( - expect.arrayContaining([ - expect.objectContaining({ name: upperCaseFirst(dmlEntity.name) }), - expect.objectContaining({ name: upperCaseFirst(entityModel.name) }), + expect(resources).toBeDefined() + expect(resources.services).toHaveLength(1) + expect(resources.services[0]).toEqual(ModuleService) + expect(resources.models).toHaveLength(2) + expect(resources.models).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: upperCaseFirst(EntityModel.name) }), + expect.objectContaining({ name: upperCaseFirst(Entity2.name) }), + ]) + ) + expect(resources.repositories).toHaveLength(0) + expect(resources.loaders).toHaveLength(2) + expect(resources.loaders).toEqual([ + expect.objectContaining({ name: "connectionLoader" }), + expect.objectContaining({ name: "containerLoader" }), ]) - ) - expect(resources.repositories).toHaveLength(0) - expect(resources.loaders).toHaveLength(2) - expect(resources.loaders).toEqual([ - expect.objectContaining({ name: "connectionLoader" }), - expect.objectContaining({ name: "containerLoader" }), - ]) - expect(resources.moduleService).toEqual(ModuleService) - - expect( - (resources.moduleService.prototype as IModuleService).__joinerConfig - ).toBeDefined() - - const generatedJoinerConfig = ( - resources.moduleService.prototype as IModuleService - ).__joinerConfig?.()! - - expect(generatedJoinerConfig).toEqual( - expect.objectContaining({ - serviceName: "module-with-dml-without-joiner-config", + expect(resources.moduleService).toEqual(ModuleService) + + const generatedJoinerConfig = ( + resources.moduleService.prototype as IModuleService + ).__joinerConfig?.()! + + expect(generatedJoinerConfig).toEqual({ + serviceName: "module-service", primaryKeys: ["id"], - linkableKeys: { - entity_model_id: "EntityModel", - dml_entity_id: "DmlEntity", - }, + linkableKeys: {}, + schema: "", alias: [ { - name: ["entity_model", "entity_models"], - entity: "EntityModel", + name: ["custom_name"], + entity: "Custom", args: { - methodSuffix: "EntityModels", - }, - }, - { - name: ["dml_entity", "dml_entities"], - entity: "DmlEntity", - args: { - methodSuffix: "DmlEntities", + methodSuffix: "Customs", }, }, ], }) - ) + }) }) + }) - test("should return the correct resources and generate the correct joiner config from mikro orm entities", async () => { - const { ModuleService, EntityModel, Entity2 } = - ModuleWithoutJoinerConfigFixtures - + describe("loadInternalModule", () => { + test("should load the module and its providers using their identifier", async () => { const moduleResolution: ModuleResolution = { resolutionPath: join( __dirname, - "../__fixtures__/module-without-joiner-config" + "../__fixtures__/module-with-providers" ), + moduleDeclaration: { + scope: "internal", + resources: "shared", + }, definition: { - key: "module-without-joiner-config", - label: "Module without joiner config", + key: "module-with-providers", + label: "Module with providers", defaultPackage: false, defaultModuleDeclaration: { scope: "internal", resources: "shared", }, }, + options: { + providers: [ + { + resolve: join( + __dirname, + "../__fixtures__/module-with-providers/provider-1" + ), + id: "provider-1-id", + options: { + api_key: "test", + }, + }, + ], + }, } - expect( - (ModuleService.prototype as IModuleService).__joinerConfig - ).toBeUndefined() - - const resources = await loadResources({ - moduleResolution, - discoveryPath: moduleResolution.resolutionPath as string, + const container = createMedusaContainer() + await loadInternalModule({ + container: container, + resolution: moduleResolution, + logger: console as any, }) - expect(resources).toBeDefined() - expect(resources.services).toHaveLength(1) - expect(resources.services[0]).toEqual(ModuleService) - expect(resources.models).toHaveLength(2) - expect(resources.models).toEqual( - expect.arrayContaining([ - expect.objectContaining({ name: upperCaseFirst(EntityModel.name) }), - expect.objectContaining({ name: upperCaseFirst(Entity2.name) }), - ]) - ) - expect(resources.repositories).toHaveLength(0) - expect(resources.loaders).toHaveLength(2) - expect(resources.loaders).toEqual([ - expect.objectContaining({ name: "connectionLoader" }), - expect.objectContaining({ name: "containerLoader" }), - ]) - expect(resources.moduleService).toEqual(ModuleService) - - expect( - (resources.moduleService.prototype as IModuleService).__joinerConfig - ).toBeDefined() - - const generatedJoinerConfig = ( - resources.moduleService.prototype as IModuleService - ).__joinerConfig?.()! - - expect(generatedJoinerConfig).toEqual({ - serviceName: "module-without-joiner-config", - primaryKeys: ["id"], - linkableKeys: { - entity2_id: "Entity2", - entity_model_id: "EntityModel", - }, - schema: "", - alias: [ - { - name: ["entity2", "entity2s"], - entity: "Entity2", - args: { - methodSuffix: "Entity2s", - }, - }, - { - name: ["entity_model", "entity_models"], - entity: "EntityModel", - args: { - methodSuffix: "EntityModels", - }, - }, - ], - }) - }) + const moduleService = container.resolve(moduleResolution.definition.key) + const provider = (moduleService as any).container[ + getProviderRegistrationKey( + ModuleServiceWithProviderProvider1.identifier + ) + ] - test("should return the correct resources and use the given joiner config", async () => { - const { ModuleService, EntityModel, Entity2 } = - ModuleWithJoinerConfigFixtures + expect(moduleService).toBeInstanceOf(ModuleServiceWithProvider) + expect(provider).toBeInstanceOf(ModuleServiceWithProviderProvider1) + }) + test("should load the module and its providers using the provided id", async () => { const moduleResolution: ModuleResolution = { resolutionPath: join( __dirname, - "../__fixtures__/module-with-joiner-config" + "../__fixtures__/module-with-providers" ), + moduleDeclaration: { + scope: "internal", + resources: "shared", + }, definition: { - key: "module-without-joiner-config", - label: "Module without joiner config", + key: "module-with-providers", + label: "Module with providers", defaultPackage: false, defaultModuleDeclaration: { scope: "internal", resources: "shared", }, }, + options: { + providers: [ + { + resolve: join( + __dirname, + "../__fixtures__/module-with-providers/provider-2" + ), + id: "provider-2-id", + options: { + api_key: "test", + }, + }, + ], + }, } - expect( - (ModuleService.prototype as IModuleService).__joinerConfig - ).toBeDefined() - - const resources = await loadResources({ - moduleResolution, - discoveryPath: moduleResolution.resolutionPath as string, + const container = createMedusaContainer() + await loadInternalModule({ + container: container, + resolution: moduleResolution, + logger: console as any, }) - expect(resources).toBeDefined() - expect(resources.services).toHaveLength(1) - expect(resources.services[0]).toEqual(ModuleService) - expect(resources.models).toHaveLength(2) - expect(resources.models).toEqual( - expect.arrayContaining([ - expect.objectContaining({ name: upperCaseFirst(EntityModel.name) }), - expect.objectContaining({ name: upperCaseFirst(Entity2.name) }), - ]) - ) - expect(resources.repositories).toHaveLength(0) - expect(resources.loaders).toHaveLength(2) - expect(resources.loaders).toEqual([ - expect.objectContaining({ name: "connectionLoader" }), - expect.objectContaining({ name: "containerLoader" }), - ]) - expect(resources.moduleService).toEqual(ModuleService) - - const generatedJoinerConfig = ( - resources.moduleService.prototype as IModuleService - ).__joinerConfig?.()! - - expect(generatedJoinerConfig).toEqual({ - serviceName: "module-service", - primaryKeys: ["id"], - linkableKeys: {}, - schema: "", - alias: [ - { - name: ["custom_name"], - entity: "Custom", - args: { - methodSuffix: "Customs", - }, - }, - ], - }) + const moduleService = container.resolve(moduleResolution.definition.key) + const provider = (moduleService as any).container[ + getProviderRegistrationKey(moduleResolution.options!.providers![0].id) + ] + + expect(moduleService).toBeInstanceOf(ModuleServiceWithProvider) + expect(provider).toBeInstanceOf(ModuleServiceWithProviderProvider2) }) }) }) diff --git a/packages/core/modules-sdk/src/loaders/utils/load-internal.ts b/packages/core/modules-sdk/src/loaders/utils/load-internal.ts index 04417a20b4686..dd804b9d1bfcf 100644 --- a/packages/core/modules-sdk/src/loaders/utils/load-internal.ts +++ b/packages/core/modules-sdk/src/loaders/utils/load-internal.ts @@ -7,6 +7,9 @@ import { MedusaContainer, ModuleExports, ModuleLoaderFunction, + ModuleProvider, + ModuleProviderExports, + ModuleProviderLoaderFunction, ModuleResolution, } from "@medusajs/types" import { @@ -15,6 +18,8 @@ import { defineJoinerConfig, DmlEntity, dynamicImport, + isString, + MedusaModuleProviderType, MedusaModuleType, ModulesSdkUtils, toMikroOrmEntities, @@ -29,7 +34,7 @@ type ModuleResource = { services: Function[] models: Function[] repositories: Function[] - loaders: ModuleLoaderFunction[] + loaders: ModuleLoaderFunction[] | ModuleProviderLoaderFunction[] moduleService: Constructor normalizedPath: string } @@ -39,22 +44,36 @@ type MigrationFunction = ( moduleDeclaration?: InternalModuleDeclaration ) => Promise +type ResolvedModule = ModuleExports & { + discoveryPath: string +} + +type ResolvedModuleProvider = ModuleProviderExports & { + discoveryPath: string +} + +export const moduleProviderRegistrationKeyPrefix = "__providers__" + +/** + * Return the key used to register a module provider in the container + * @param {string} moduleKey + * @return {string} + */ +export function getProviderRegistrationKey(moduleKey: string): string { + return moduleProviderRegistrationKeyPrefix + moduleKey +} + export async function resolveModuleExports({ resolution, }: { resolution: ModuleResolution -}): Promise< - | (ModuleExports & { - discoveryPath: string - }) - | { error: any } -> { +}): Promise { let resolvedModuleExports: ModuleExports try { if (resolution.moduleExports) { // TODO: // If we want to benefit from the auto load mechanism, even if the module exports is provided, we need to ask for the module path - resolvedModuleExports = resolution.moduleExports + resolvedModuleExports = resolution.moduleExports as ModuleExports resolvedModuleExports.discoveryPath = resolution.resolutionPath as string } else { const module = await dynamicImport(resolution.resolutionPath as string) @@ -62,10 +81,12 @@ export async function resolveModuleExports({ if ("discoveryPath" in module) { const reExportedLoadedModule = await dynamicImport(module.discoveryPath) const discoveryPath = module.discoveryPath - resolvedModuleExports = reExportedLoadedModule.default + resolvedModuleExports = + reExportedLoadedModule.default ?? reExportedLoadedModule resolvedModuleExports.discoveryPath = discoveryPath as string } else { - resolvedModuleExports = (module as { default: ModuleExports }).default + resolvedModuleExports = + (module as { default: ModuleExports }).default ?? module resolvedModuleExports.discoveryPath = resolution.resolutionPath as string } @@ -90,13 +111,83 @@ export async function resolveModuleExports({ } } -export async function loadInternalModule( - container: MedusaContainer, - resolution: ModuleResolution, - logger: Logger, - migrationOnly?: boolean, - loaderOnly?: boolean +async function loadInternalProvider( + args: { + container: MedusaContainer + resolution: ModuleResolution + logger: Logger + migrationOnly?: boolean + loaderOnly?: boolean + }, + providers: ModuleProvider[] ): Promise<{ error?: Error } | void> { + const { container, resolution, logger, migrationOnly } = args + + const errors: { error?: Error }[] = [] + for (const provider of providers) { + const providerRes = provider.resolve as ModuleProviderExports + + const canLoadProvider = + providerRes && (isString(providerRes) || !providerRes?.services) + + if (!canLoadProvider) { + continue + } + + const res = await loadInternalModule({ + container, + resolution: { + ...resolution, + moduleExports: !isString(providerRes) ? providerRes : undefined, + definition: { + ...resolution.definition, + key: provider.id, + }, + resolutionPath: isString(provider.resolve) + ? require.resolve(provider.resolve, { + paths: [process.cwd()], + }) + : false, + }, + logger, + migrationOnly, + loadingProviders: true, + }) + + if (res) { + errors.push(res) + } + } + + const errorMessages = errors.map((e) => e.error?.message).join("\n") + return errors.length + ? { + error: { + name: "ModuleProviderError", + message: `Errors while loading module providers for module ${resolution.definition.key}:\n${errorMessages}`, + stack: errors.map((e) => e.error?.stack).join("\n"), + }, + } + : undefined +} + +export async function loadInternalModule(args: { + container: MedusaContainer + resolution: ModuleResolution + logger: Logger + migrationOnly?: boolean + loaderOnly?: boolean + loadingProviders?: boolean +}): Promise<{ error?: Error } | void> { + const { + container, + resolution, + logger, + migrationOnly, + loaderOnly, + loadingProviders, + } = args + const keyName = !loaderOnly ? resolution.definition.key : resolution.definition.key + "__loaderOnly" @@ -121,7 +212,12 @@ export async function loadInternalModule( }) } - if (!loadedModule?.service && !moduleResources.moduleService) { + const loadedModule_ = loadedModule as ModuleExports + if ( + !loadingProviders && + !loadedModule_?.service && + !moduleResources.moduleService + ) { container.register({ [keyName]: asValue(undefined), }) @@ -133,20 +229,6 @@ export async function loadInternalModule( } } - if (migrationOnly) { - const moduleService_ = moduleResources.moduleService ?? loadedModule.service - - // Partially loaded module, only register the service __joinerConfig function to be able to resolve it later - const moduleService = { - __joinerConfig: moduleService_.prototype.__joinerConfig, - } - - container.register({ - [keyName]: asValue(moduleService), - }) - return - } - const localContainer = createMedusaContainer() const dependencies = resolution?.dependencies ?? [] @@ -177,6 +259,44 @@ export async function loadInternalModule( ) } + // if module has providers, load them + let providerOptions: any = undefined + if (!loadingProviders) { + const providers = (resolution?.options?.providers as any[]) ?? [] + + const res = await loadInternalProvider( + { + ...args, + container: localContainer, + }, + providers + ) + + if (res?.error) { + return res + } + } else { + providerOptions = (resolution?.options?.providers as any[]).find( + (p) => p.id === resolution.definition.key + )?.options + } + + if (migrationOnly && !loadingProviders) { + const moduleService_ = + moduleResources.moduleService ?? loadedModule_.service + + // Partially loaded module, only register the service __joinerConfig function to be able to resolve it later + const moduleService = { + __joinerConfig: moduleService_.prototype.__joinerConfig, + } + + container.register({ + [keyName]: asValue(moduleService), + }) + + return + } + const loaders = moduleResources.loaders ?? loadedModule?.loaders ?? [] const error = await runLoaders(loaders, { container, @@ -185,24 +305,56 @@ export async function loadInternalModule( resolution, loaderOnly, keyName, + providerOptions, }) if (error) { return error } - const moduleService = moduleResources.moduleService ?? loadedModule.service + if (loadingProviders) { + const loadedProvider_ = loadedModule as ModuleProviderExports - container.register({ - [keyName]: asFunction((cradle) => { - ;(moduleService as any).__type = MedusaModuleType - return new moduleService( - localContainer.cradle, - resolution.options, - resolution.moduleDeclaration + let moduleProviderServices = moduleResources.moduleService + ? [moduleResources.moduleService] + : loadedProvider_.services ?? loadedProvider_ + + if (!moduleProviderServices) { + return + } + + for (const moduleProviderService of moduleProviderServices) { + const modProvider_ = moduleProviderService as any + + modProvider_.identifier ??= keyName + modProvider_.__type = MedusaModuleProviderType + const registrationKey = getProviderRegistrationKey( + modProvider_.identifier ) - }).singleton(), - }) + container.register({ + [registrationKey]: asFunction((cradle) => { + ;(moduleProviderService as any).__type = MedusaModuleType + return new moduleProviderService( + localContainer.cradle, + resolution.options, + resolution.moduleDeclaration + ) + }).singleton(), + }) + } + } else { + const moduleService = moduleResources.moduleService ?? loadedModule_.service + container.register({ + [keyName]: asFunction((cradle) => { + ;(moduleService as any).__type = MedusaModuleType + return new moduleService( + localContainer.cradle, + resolution.options, + resolution.moduleDeclaration + ) + }).singleton(), + }) + } if (loaderOnly) { // The expectation is only to run the loader as standalone, so we do not need to register the service and we need to cleanup all services @@ -220,48 +372,124 @@ export async function loadModuleMigrations( revertMigration?: MigrationFunction generateMigration?: MigrationFunction }> { - const loadedModule = await resolveModuleExports({ - resolution: { ...resolution, moduleExports }, - }) - - if ("error" in loadedModule) { - throw loadedModule.error - } + const runMigrationsFn: ((...args) => Promise)[] = [] + const revertMigrationFn: ((...args) => Promise)[] = [] + const generateMigrationFn: ((...args) => Promise)[] = [] try { - let runMigrations = loadedModule.runMigrations - let revertMigration = loadedModule.revertMigration - let generateMigration = loadedModule.generateMigration - - if (!runMigrations || !revertMigration) { - const moduleResources = await loadResources({ - moduleResolution: resolution, - discoveryPath: loadedModule.discoveryPath, - loadedModuleLoaders: loadedModule?.loaders, - }) + const mainLoadedModule = await resolveModuleExports({ + resolution: { ...resolution, moduleExports }, + }) + if ("error" in mainLoadedModule) { + throw mainLoadedModule.error + } - const migrationScriptOptions = { - moduleName: resolution.definition.key, - models: moduleResources.models, - pathToMigrations: join(moduleResources.normalizedPath, "migrations"), + const loadedServices = [mainLoadedModule] as ( + | ResolvedModule + | ResolvedModuleProvider + )[] + + if (Array.isArray(resolution?.options?.providers)) { + for (const provider of (resolution.options as any).providers) { + const providerRes = provider.resolve as ModuleProviderExports + + const canLoadProvider = + providerRes && (isString(providerRes) || !providerRes?.services) + + if (!canLoadProvider) { + continue + } + + const loadedProvider = await resolveModuleExports({ + resolution: { + ...resolution, + moduleExports: !isString(providerRes) ? providerRes : undefined, + definition: { + ...resolution.definition, + key: provider.id, + }, + resolutionPath: isString(provider.resolve) + ? provider.resolve + : false, + }, + }) + + if ("error" in loadedProvider) { + throw loadedProvider.error + } + + loadedServices.push(loadedProvider as ResolvedModuleProvider) } + } - runMigrations ??= ModulesSdkUtils.buildMigrationScript( - migrationScriptOptions - ) + const migrationScripts: any[] = [] + for (const loadedModule of loadedServices) { + let runMigrationsCustom = loadedModule.runMigrations + let revertMigrationCustom = loadedModule.revertMigration + let generateMigrationCustom = loadedModule.generateMigration + + runMigrationsCustom && runMigrationsFn.push(runMigrationsCustom) + revertMigrationCustom && revertMigrationFn.push(revertMigrationCustom) + generateMigrationCustom && + generateMigrationFn.push(generateMigrationCustom) + + if (!runMigrationsCustom || !revertMigrationCustom) { + const moduleResources = await loadResources({ + moduleResolution: resolution, + discoveryPath: loadedModule.discoveryPath, + loadedModuleLoaders: loadedModule?.loaders, + }) - revertMigration ??= ModulesSdkUtils.buildRevertMigrationScript( - migrationScriptOptions - ) + migrationScripts.push({ + moduleName: resolution.definition.key, + models: moduleResources.models, + pathToMigrations: join(moduleResources.normalizedPath, "migrations"), + }) + } - generateMigration ??= ModulesSdkUtils.buildGenerateMigrationScript( - migrationScriptOptions - ) + for (const migrationScriptOptions of migrationScripts) { + const migrationUp = + runMigrationsCustom ?? + ModulesSdkUtils.buildMigrationScript(migrationScriptOptions) + runMigrationsFn.push(migrationUp) + + const migrationDown = + revertMigrationCustom ?? + ModulesSdkUtils.buildRevertMigrationScript(migrationScriptOptions) + revertMigrationFn.push(migrationDown) + + const genMigration = + generateMigrationCustom ?? + ModulesSdkUtils.buildGenerateMigrationScript(migrationScriptOptions) + generateMigrationFn.push(genMigration) + } } - return { runMigrations, revertMigration, generateMigration } - } catch { - return {} + const runMigrations = async (...args) => { + for (const migration of runMigrationsFn.filter(Boolean)) { + await migration.apply(migration, args) + } + } + const revertMigration = async (...args) => { + for (const migration of revertMigrationFn.filter(Boolean)) { + await migration.apply(migration, args) + } + } + const generateMigration = async (...args) => { + for (const migration of generateMigrationFn.filter(Boolean)) { + await migration.apply(migration, args) + } + } + + return { + runMigrations, + revertMigration, + generateMigration, + } + } catch (e) { + throw new Error( + `Unable to resolve the migration scripts for the module ${resolution.definition.key}\n${e.message}\n${e.stack}` + ) } } @@ -308,7 +536,7 @@ export async function loadResources({ moduleResolution: ModuleResolution discoveryPath: string logger?: Logger - loadedModuleLoaders?: ModuleLoaderFunction[] + loadedModuleLoaders?: ModuleLoaderFunction[] | ModuleProviderLoaderFunction[] }): Promise { logger ??= console as unknown as Logger loadedModuleLoaders ??= [] @@ -324,7 +552,8 @@ export async function loadResources({ const [moduleService, services, models, repositories] = await Promise.all([ dynamicImport(modulePath).then((moduleExports) => { - return moduleExports.default.service + const mod = moduleExports.default ?? moduleExports + return mod.service }), importAllFromDir(resolve(normalizedPath, "services")).catch( defaultOnFail @@ -365,11 +594,14 @@ export async function loadResources({ migrationPath: normalizedPath + "/migrations", }) - generateJoinerConfigIfNecessary({ - moduleResolution, - service: moduleService, - models: potentialModels, - }) + // if a module service is provided, we generate a joiner config + if (moduleService) { + generateJoinerConfigIfNecessary({ + moduleResolution, + service: moduleService, + models: potentialModels, + }) + } return { services: potentialServices, @@ -390,7 +622,15 @@ export async function loadResources({ async function runLoaders( loaders: Function[] = [], - { localContainer, container, logger, resolution, loaderOnly, keyName } + { + localContainer, + container, + logger, + resolution, + loaderOnly, + keyName, + providerOptions, + } ): Promise { try { for (const loader of loaders) { @@ -398,8 +638,9 @@ async function runLoaders( { container: localContainer, logger, - options: resolution.options, + options: providerOptions ?? resolution.options, dataLoaderOnly: loaderOnly, + moduleOptions: providerOptions ? resolution.options : undefined, }, resolution.moduleDeclaration as InternalModuleDeclaration ) @@ -418,14 +659,17 @@ async function runLoaders( } function prepareLoaders({ - loadedModuleLoaders = [] as ModuleLoaderFunction[], + loadedModuleLoaders = [] as + | ModuleLoaderFunction[] + | ModuleProviderLoaderFunction[], models, repositories, services, moduleResolution, migrationPath, }) { - const finalLoaders: ModuleLoaderFunction[] = [] + const finalLoaders: (ModuleLoaderFunction | ModuleProviderLoaderFunction)[] = + [] const toObjectReducer = (acc, curr) => { acc[curr.name] = curr diff --git a/packages/core/modules-sdk/src/medusa-app.ts b/packages/core/modules-sdk/src/medusa-app.ts index 0b7e4ad904555..0c737fbe7dd62 100644 --- a/packages/core/modules-sdk/src/medusa-app.ts +++ b/packages/core/modules-sdk/src/medusa-app.ts @@ -510,7 +510,7 @@ async function MedusaApp_({ modulePath: moduleResolution.resolutionPath as string, container: sharedContainer, options: moduleResolution.options, - moduleExports: moduleResolution.moduleExports, + moduleExports: moduleResolution.moduleExports as ModuleExports, } if (action === "revert") { diff --git a/packages/core/types/src/common/config-module.ts b/packages/core/types/src/common/config-module.ts index e351ac678ea57..fcfc03a01c8aa 100644 --- a/packages/core/types/src/common/config-module.ts +++ b/packages/core/types/src/common/config-module.ts @@ -49,21 +49,6 @@ export interface AdminOptions { * ``` */ path: `/${string}` - /** - * The directory where the admin build is outputted when you run the `build` command. - * The default value is `./build`. - * - * @example - * ```js title="medusa-config.js" - * module.exports = defineConfig({ - * admin: { - * outDir: process.env.ADMIN_BUILD_DIR || `./build`, - * }, - * // ... - * }) - * ``` - */ - outDir: string /** * The URL of your Medusa application. This is useful to set when you deploy the Medusa application. * diff --git a/packages/core/types/src/modules-sdk/index.ts b/packages/core/types/src/modules-sdk/index.ts index e953c9de25e9b..6713449154873 100644 --- a/packages/core/types/src/modules-sdk/index.ts +++ b/packages/core/types/src/modules-sdk/index.ts @@ -3,6 +3,7 @@ import { JoinerRelationship, JoinerServiceConfig } from "../joiner" import { MedusaContainer } from "../common" import { RepositoryService } from "../dal" import { Logger } from "../logger" +import { ModuleProviderExports } from "./module-provider" import { RemoteQueryGraph, RemoteQueryInput, @@ -86,7 +87,7 @@ export type ModuleResolution = { options?: Record dependencies?: string[] moduleDeclaration?: InternalModuleDeclaration | ExternalModuleDeclaration - moduleExports?: ModuleExports + moduleExports?: ModuleExports | ModuleProviderExports } export type ModuleDefinition = { diff --git a/packages/core/types/src/modules-sdk/module-provider.ts b/packages/core/types/src/modules-sdk/module-provider.ts index 33524247abe2a..851154fd66887 100644 --- a/packages/core/types/src/modules-sdk/module-provider.ts +++ b/packages/core/types/src/modules-sdk/module-provider.ts @@ -1,11 +1,47 @@ -import { Constructor } from "./index" +import { Logger } from "../logger" +import { + Constructor, + InternalModuleDeclaration, + MedusaContainer, +} from "./index" -export type ModuleProviderExports = { - services: Constructor[] +export type ProviderLoaderOptions> = { + container: MedusaContainer + options?: TOptions + logger?: Logger + moduleOptions: Record } +export type ModuleProviderExports = { + module?: string + services: Constructor[] + loaders?: ModuleProviderLoaderFunction[] + runMigrations?( + options: ProviderLoaderOptions, + moduleDeclaration?: any + ): Promise + revertMigration?( + options: ProviderLoaderOptions, + moduleDeclaration?: any + ): Promise + generateMigration?( + options: ProviderLoaderOptions, + moduleDeclaration?: any + ): Promise + /** + * Explicitly set the the true location of the module resources. + * Can be used to re-export the module from a different location and specify its original location. + */ + discoveryPath?: string +} + +export type ModuleProviderLoaderFunction = ( + options: ProviderLoaderOptions, + moduleDeclaration?: InternalModuleDeclaration +) => Promise + export type ModuleProvider = { - resolve: string | ModuleProviderExports + resolve: string | ModuleProviderExports id: string options?: Record is_default?: boolean diff --git a/packages/core/utils/src/common/__tests__/define-config.spec.ts b/packages/core/utils/src/common/__tests__/define-config.spec.ts index eaea563ed05ff..929b432859f55 100644 --- a/packages/core/utils/src/common/__tests__/define-config.spec.ts +++ b/packages/core/utils/src/common/__tests__/define-config.spec.ts @@ -7,7 +7,6 @@ describe("defineConfig", function () { { "admin": { "backendUrl": "http://localhost:9000", - "outDir": ".medusa/admin", "path": "/app", }, "featureFlags": {}, @@ -66,6 +65,9 @@ describe("defineConfig", function () { "inventory": { "resolve": "@medusajs/medusa/inventory-next", }, + "locking": { + "resolve": "@medusajs/medusa/locking", + }, "notification": { "options": { "providers": [ @@ -151,7 +153,6 @@ describe("defineConfig", function () { { "admin": { "backendUrl": "http://localhost:9000", - "outDir": ".medusa/admin", "path": "/app", }, "featureFlags": {}, @@ -213,6 +214,9 @@ describe("defineConfig", function () { "inventory": { "resolve": "@medusajs/medusa/inventory-next", }, + "locking": { + "resolve": "@medusajs/medusa/locking", + }, "notification": { "options": { "providers": [ @@ -301,7 +305,6 @@ describe("defineConfig", function () { { "admin": { "backendUrl": "http://localhost:9000", - "outDir": ".medusa/admin", "path": "/app", }, "featureFlags": {}, @@ -368,6 +371,9 @@ describe("defineConfig", function () { "inventory": { "resolve": "@medusajs/medusa/inventory-next", }, + "locking": { + "resolve": "@medusajs/medusa/locking", + }, "notification": { "options": { "providers": [ @@ -457,7 +463,6 @@ describe("defineConfig", function () { { "admin": { "backendUrl": "http://localhost:9000", - "outDir": ".medusa/admin", "path": "/app", }, "featureFlags": {}, @@ -524,6 +529,9 @@ describe("defineConfig", function () { "inventory": { "resolve": "@medusajs/medusa/inventory-next", }, + "locking": { + "resolve": "@medusajs/medusa/locking", + }, "notification": { "options": { "providers": [ @@ -609,7 +617,6 @@ describe("defineConfig", function () { { "admin": { "backendUrl": "http://localhost:9000", - "outDir": ".medusa/admin", "path": "/app", }, "featureFlags": {}, @@ -668,6 +675,9 @@ describe("defineConfig", function () { "inventory": { "resolve": "@medusajs/medusa/inventory-next", }, + "locking": { + "resolve": "@medusajs/medusa/locking", + }, "notification": { "options": { "providers": [ @@ -756,7 +766,6 @@ describe("defineConfig", function () { { "admin": { "backendUrl": "http://localhost:9000", - "outDir": ".medusa/admin", "path": "/app", }, "featureFlags": {}, @@ -812,6 +821,9 @@ describe("defineConfig", function () { "inventory": { "resolve": "@medusajs/medusa/inventory-next", }, + "locking": { + "resolve": "@medusajs/medusa/locking", + }, "notification": { "options": { "providers": [ diff --git a/packages/core/utils/src/common/define-config.ts b/packages/core/utils/src/common/define-config.ts index db2e48991008f..ae962cc674378 100644 --- a/packages/core/utils/src/common/define-config.ts +++ b/packages/core/utils/src/common/define-config.ts @@ -8,10 +8,10 @@ import { Modules, REVERSED_MODULE_PACKAGE_NAMES, } from "../modules-sdk" -import { isString } from "./is-string" -import { resolveExports } from "./resolve-exports" import { isObject } from "./is-object" +import { isString } from "./is-string" import { normalizeImportPathWithSource } from "./normalize-import-path-with-source" +import { resolveExports } from "./resolve-exports" const DEFAULT_SECRET = "supersecret" const DEFAULT_ADMIN_URL = "http://localhost:9000" @@ -90,7 +90,6 @@ export function defineConfig(config: Config = {}): ConfigModule { */ const admin: ConfigModule["admin"] = { backendUrl: process.env.MEDUSA_BACKEND_URL || DEFAULT_ADMIN_URL, - outDir: ".medusa/admin", path: "/app", ...config.admin, } @@ -132,6 +131,7 @@ function resolveModules( { resolve: MODULE_PACKAGE_NAMES[Modules.CACHE] }, { resolve: MODULE_PACKAGE_NAMES[Modules.EVENT_BUS] }, { resolve: MODULE_PACKAGE_NAMES[Modules.WORKFLOW_ENGINE] }, + { resolve: MODULE_PACKAGE_NAMES[Modules.LOCKING] }, { resolve: MODULE_PACKAGE_NAMES[Modules.STOCK_LOCATION] }, { resolve: MODULE_PACKAGE_NAMES[Modules.INVENTORY] }, { resolve: MODULE_PACKAGE_NAMES[Modules.PRODUCT] }, diff --git a/packages/core/utils/src/common/get-config-file.ts b/packages/core/utils/src/common/get-config-file.ts index 1983f781b794d..3438f312a6159 100644 --- a/packages/core/utils/src/common/get-config-file.ts +++ b/packages/core/utils/src/common/get-config-file.ts @@ -1,4 +1,5 @@ import { join } from "path" +import { dynamicImport } from "./dynamic-import" /** * Attempts to resolve the config file in a given root directory. @@ -6,22 +7,23 @@ import { join } from "path" * @param {string} configName - the name of the config file. * @return {object} an object containing the config module and its path as well as an error property if the config couldn't be loaded. */ -export function getConfigFile( +export async function getConfigFile( rootDir: string, configName: string -): +): Promise< | { configModule: null; configFilePath: string; error: Error } - | { configModule: TConfig; configFilePath: string; error: null } { + | { configModule: TConfig; configFilePath: string; error: null } +> { const configPath = join(rootDir, configName) try { - const configFilePath = require.resolve(configPath) - const configExports = require(configFilePath) + const configFilePath = join(process.cwd(), rootDir, configName) + const resolvedExports = await dynamicImport(configPath) return { configModule: - configExports && "default" in configExports - ? configExports.default - : configExports, + "default" in resolvedExports && resolvedExports.default + ? resolvedExports.default + : resolvedExports, configFilePath, error: null, } diff --git a/packages/core/utils/src/index.ts b/packages/core/utils/src/index.ts index 6510e4470c878..5142f288daef5 100644 --- a/packages/core/utils/src/index.ts +++ b/packages/core/utils/src/index.ts @@ -1,4 +1,3 @@ -export * from "./graphql" export * from "./api-key" export * from "./auth" export * from "./bundles" @@ -12,6 +11,7 @@ export * from "./exceptions" export * from "./feature-flags" export * from "./file" export * from "./fulfillment" +export * from "./graphql" export * from "./inventory" export * from "./link" export * from "./modules-sdk" @@ -30,3 +30,4 @@ export * from "./totals/big-number" export * from "./user" export const MedusaModuleType = Symbol.for("MedusaModule") +export const MedusaModuleProviderType = Symbol.for("MedusaModuleProvider") diff --git a/packages/core/utils/src/modules-sdk/definition.ts b/packages/core/utils/src/modules-sdk/definition.ts index 25ff88a3639c4..7582f00f45669 100644 --- a/packages/core/utils/src/modules-sdk/definition.ts +++ b/packages/core/utils/src/modules-sdk/definition.ts @@ -63,6 +63,13 @@ export const REVERSED_MODULE_PACKAGE_NAMES = Object.entries( return acc }, {}) +// TODO: temporary fix until the event bus, cache and workflow engine are migrated to use providers and therefore only a single resolution will be good +REVERSED_MODULE_PACKAGE_NAMES["@medusajs/medusa/event-bus-redis"] = + Modules.EVENT_BUS +REVERSED_MODULE_PACKAGE_NAMES["@medusajs/medusa/cache-redis"] = Modules.CACHE +REVERSED_MODULE_PACKAGE_NAMES["@medusajs/medusa/workflow-engine-redis"] = + Modules.WORKFLOW_ENGINE + /** * Making modules be referenced as a type as well. */ diff --git a/packages/core/utils/src/modules-sdk/index.ts b/packages/core/utils/src/modules-sdk/index.ts index d5542028da117..c2df880e8716b 100644 --- a/packages/core/utils/src/modules-sdk/index.ts +++ b/packages/core/utils/src/modules-sdk/index.ts @@ -15,6 +15,7 @@ export * from "./medusa-service" export * from "./migration-scripts" export * from "./mikro-orm-cli-config-builder" export * from "./module" +export * from "./module-provider" export * from "./query-context" export * from "./types/links-config" export * from "./types/medusa-service" diff --git a/packages/core/utils/src/modules-sdk/module-provider.ts b/packages/core/utils/src/modules-sdk/module-provider.ts new file mode 100644 index 0000000000000..4381fd952bf27 --- /dev/null +++ b/packages/core/utils/src/modules-sdk/module-provider.ts @@ -0,0 +1,19 @@ +import { ModuleProviderExports } from "@medusajs/types" + +/** + * Wrapper to build the module provider export + * + * @param serviceName // The name of the module the provider is for + * @param services // The array of services that the module provides + * @param loaders // The loaders that the module provider provides + */ +export function ModuleProvider( + serviceName: string, + { services, loaders }: ModuleProviderExports +): ModuleProviderExports { + return { + module: serviceName, + services, + loaders, + } +} diff --git a/packages/medusa-test-utils/src/medusa-test-runner-utils/config.ts b/packages/medusa-test-utils/src/medusa-test-runner-utils/config.ts index c4b2ee68a2b76..a617c3d8de396 100644 --- a/packages/medusa-test-utils/src/medusa-test-runner-utils/config.ts +++ b/packages/medusa-test-utils/src/medusa-test-runner-utils/config.ts @@ -5,7 +5,7 @@ export async function configLoaderOverride( override: { clientUrl: string; debug?: boolean } ) { const { configManager } = await import("@medusajs/framework/config") - const { configModule, error } = getConfigFile< + const { configModule, error } = await getConfigFile< ReturnType >(entryDirectory, "medusa-config") diff --git a/packages/medusa/package.json b/packages/medusa/package.json index 5c510c282a5d3..1d611aa7f190d 100644 --- a/packages/medusa/package.json +++ b/packages/medusa/package.json @@ -84,6 +84,7 @@ "@medusajs/inventory-next": "^0.0.3", "@medusajs/link-modules": "^0.2.11", "@medusajs/locking": "^0.0.1", + "@medusajs/locking-redis": "^0.0.1", "@medusajs/notification": "^0.1.2", "@medusajs/notification-local": "^0.0.1", "@medusajs/notification-sendgrid": "^0.0.1", diff --git a/packages/medusa/src/commands/build.ts b/packages/medusa/src/commands/build.ts index 79d080e8fd88c..7c95d962f8bb9 100644 --- a/packages/medusa/src/commands/build.ts +++ b/packages/medusa/src/commands/build.ts @@ -1,13 +1,38 @@ import path from "path" -import { rm, copyFile, access, constants } from "node:fs/promises" +import { access, constants, copyFile, rm } from "node:fs/promises" import type tsStatic from "typescript" import { logger } from "@medusajs/framework/logger" import { ConfigModule } from "@medusajs/framework/types" import { getConfigFile } from "@medusajs/framework/utils" +import { + ADMIN_ONLY_OUTPUT_DIR, + ADMIN_RELATIVE_OUTPUT_DIR, + ADMIN_SOURCE_DIR, +} from "../utils" -const ADMIN_FOLDER = "src/admin" const INTEGRATION_TESTS_FOLDER = "integration-tests" +function computeDist( + projectRoot: string, + tsConfig: { options: { outDir?: string } } +): string { + const distFolder = tsConfig.options.outDir ?? ".medusa/server" + return path.isAbsolute(distFolder) + ? distFolder + : path.join(projectRoot, distFolder) +} + +async function loadTsConfig(projectRoot: string) { + const ts = await import("typescript") + const tsConfig = parseTSConfig(projectRoot, ts) + if (!tsConfig) { + logger.error("Unable to compile backend source") + return false + } + + return tsConfig! +} + /** * Copies the file to the destination without throwing any * errors if the source file is missing @@ -39,15 +64,13 @@ async function clean(path: string) { /** * Loads the medusa config file or exits with an error */ -function loadMedusaConfig(directory: string) { +async function loadMedusaConfig(directory: string) { /** * Parsing the medusa config file to ensure it is error * free */ - const { configModule, configFilePath, error } = getConfigFile( - directory, - "medusa-config" - ) + const { configModule, configFilePath, error } = + await getConfigFile(directory, "medusa-config") if (error) { console.error(`Failed to load medusa-config.js`) console.error(error) @@ -100,21 +123,14 @@ function parseTSConfig(projectRoot: string, ts: typeof tsStatic) { /** * Builds the backend project using TSC */ -async function buildBackend(projectRoot: string): Promise { +async function buildBackend( + projectRoot: string, + tsConfig: tsStatic.ParsedCommandLine +): Promise { const startTime = process.hrtime() logger.info("Compiling backend source...") - const ts = await import("typescript") - const tsConfig = parseTSConfig(projectRoot, ts) - if (!tsConfig) { - logger.error("Unable to compile backend source") - return false - } - - const distFolder = tsConfig.options.outDir ?? ".medusa/server" - const dist = path.isAbsolute(distFolder) - ? distFolder - : path.join(projectRoot, distFolder) + const dist = computeDist(projectRoot, tsConfig) logger.info(`Removing existing "${path.relative(projectRoot, dist)}" folder`) await clean(dist) @@ -125,11 +141,12 @@ async function buildBackend(projectRoot: string): Promise { */ const filesToCompile = tsConfig.fileNames.filter((fileName) => { return ( - !fileName.includes(`${ADMIN_FOLDER}/`) && + !fileName.includes(`${ADMIN_SOURCE_DIR}/`) && !fileName.includes(`${INTEGRATION_TESTS_FOLDER}/`) ) }) + const ts = await import("typescript") const program = ts.createProgram(filesToCompile, { ...tsConfig.options, ...{ @@ -197,24 +214,41 @@ async function buildBackend(projectRoot: string): Promise { /** * Builds the frontend project using the "@medusajs/admin-bundler" */ -async function buildFrontend(projectRoot: string): Promise { +async function buildFrontend( + projectRoot: string, + adminOnly: boolean, + tsConfig: tsStatic.ParsedCommandLine +): Promise { const startTime = process.hrtime() - const configFile = loadMedusaConfig(projectRoot) + const configFile = await loadMedusaConfig(projectRoot) if (!configFile) { return false } - const adminSource = path.join(projectRoot, ADMIN_FOLDER) + const dist = computeDist(projectRoot, tsConfig) + + const adminOutputPath = adminOnly + ? path.join(projectRoot, ADMIN_ONLY_OUTPUT_DIR) + : path.join(dist, ADMIN_RELATIVE_OUTPUT_DIR) + + const adminSource = path.join(projectRoot, ADMIN_SOURCE_DIR) const adminOptions = { disable: false, sources: [adminSource], ...configFile.configModule.admin, + outDir: adminOutputPath, } - if (adminOptions.disable) { + if (adminOptions.disable && !adminOnly) { return false } + if (!adminOptions.disable && adminOnly) { + logger.warn( + `You are building using the flag --admin-only but the admin is enabled in your medusa-config, If you intend to host the dashboard separately you should disable the admin in your medusa config` + ) + } + try { logger.info("Compiling frontend source...") const { build: buildProductionBuild } = await import( @@ -233,7 +267,28 @@ async function buildFrontend(projectRoot: string): Promise { } } -export default async function ({ directory }: { directory: string }) { +export default async function ({ + directory, + adminOnly, +}: { + directory: string + adminOnly: boolean +}): Promise { logger.info("Starting build...") - await Promise.all([buildBackend(directory), buildFrontend(directory)]) + + const tsConfig = await loadTsConfig(directory) + if (!tsConfig) { + return false + } + + const promises: Promise[] = [] + + if (!adminOnly) { + promises.push(buildBackend(directory, tsConfig)) + } + + promises.push(buildFrontend(directory, adminOnly, tsConfig)) + + await Promise.all(promises) + return true } diff --git a/packages/medusa/src/loaders/admin.ts b/packages/medusa/src/loaders/admin.ts index 0d2ce30bff92a..fa126ae03bf83 100644 --- a/packages/medusa/src/loaders/admin.ts +++ b/packages/medusa/src/loaders/admin.ts @@ -3,6 +3,7 @@ import { AdminOptions, ConfigModule } from "@medusajs/framework/types" import { Express } from "express" import fs from "fs" import path from "path" +import { ADMIN_RELATIVE_OUTPUT_DIR } from "../utils" type Options = { app: Express @@ -10,10 +11,9 @@ type Options = { rootDirectory: string } -type IntializedOptions = Required< - Pick -> & +type IntializedOptions = Required> & AdminOptions & { + outDir: string sources?: string[] } @@ -39,6 +39,7 @@ export default async function adminLoader({ disable: false, sources, ...admin, + outDir: path.join(rootDirectory, ADMIN_RELATIVE_OUTPUT_DIR), } if (adminOptions?.disable) { diff --git a/packages/medusa/src/loaders/index.ts b/packages/medusa/src/loaders/index.ts index a3ec7c14b3543..dfc27634cdd26 100644 --- a/packages/medusa/src/loaders/index.ts +++ b/packages/medusa/src/loaders/index.ts @@ -118,7 +118,7 @@ async function loadEntrypoints( export async function initializeContainer( rootDirectory: string ): Promise { - configLoader(rootDirectory, "medusa-config") + await configLoader(rootDirectory, "medusa-config") await featureFlagsLoader(join(__dirname, "feature-flags")) container.register({ diff --git a/packages/medusa/src/modules/locking-redis.ts b/packages/medusa/src/modules/locking-redis.ts new file mode 100644 index 0000000000000..4c389a39e0a5d --- /dev/null +++ b/packages/medusa/src/modules/locking-redis.ts @@ -0,0 +1,6 @@ +import RedisLockingProvider from "@medusajs/locking-redis" + +export * from "@medusajs/locking-redis" + +export default RedisLockingProvider +export const discoveryPath = require.resolve("@medusajs/locking-redis") diff --git a/packages/medusa/src/utils/admin-consts.ts b/packages/medusa/src/utils/admin-consts.ts new file mode 100644 index 0000000000000..54a58297e3274 --- /dev/null +++ b/packages/medusa/src/utils/admin-consts.ts @@ -0,0 +1,3 @@ +export const ADMIN_SOURCE_DIR = "src/admin" +export const ADMIN_RELATIVE_OUTPUT_DIR = "./public/admin" +export const ADMIN_ONLY_OUTPUT_DIR = ".medusa/admin" diff --git a/packages/medusa/src/utils/index.ts b/packages/medusa/src/utils/index.ts index 35b8ff48d5a3d..c6f93f8a0202a 100644 --- a/packages/medusa/src/utils/index.ts +++ b/packages/medusa/src/utils/index.ts @@ -2,3 +2,4 @@ export * from "./clean-response-data" export * from "./exception-formatter" export * from "./middlewares" export * from "./define-middlewares" +export * from "./admin-consts" diff --git a/packages/modules/cache-redis/src/services/redis-cache.ts b/packages/modules/cache-redis/src/services/redis-cache.ts index b794dffdba1e4..217af91fa181a 100644 --- a/packages/modules/cache-redis/src/services/redis-cache.ts +++ b/packages/modules/cache-redis/src/services/redis-cache.ts @@ -76,14 +76,28 @@ class RedisCacheService implements ICacheService { * @param key */ async invalidate(key: string): Promise { - const keys = await this.redis.keys(this.getCacheKey(key)) - const pipeline = this.redis.pipeline() + const pattern = this.getCacheKey(key) + let cursor = "0" + do { + const result = await this.redis.scan( + cursor, + "MATCH", + pattern, + "COUNT", + 100 + ) + cursor = result[0] + const keys = result[1] - keys.forEach(function (key) { - pipeline.del(key) - }) + if (keys.length > 0) { + const deletePipeline = this.redis.pipeline() + for (const key of keys) { + deletePipeline.del(key) + } - await pipeline.exec() + await deletePipeline.exec() + } + } while (cursor !== "0") } /** diff --git a/packages/modules/index/integration-tests/__tests__/index-engine-module.spec.ts b/packages/modules/index/integration-tests/__tests__/index-engine-module.spec.ts index 9ea6f476c7cd0..31cc6e79197b1 100644 --- a/packages/modules/index/integration-tests/__tests__/index-engine-module.spec.ts +++ b/packages/modules/index/integration-tests/__tests__/index-engine-module.spec.ts @@ -100,7 +100,10 @@ let index!: IndexTypes.IIndexService const beforeAll_ = async () => { try { - configLoader(path.join(__dirname, "./../__fixtures__"), "medusa-config") + await configLoader( + path.join(__dirname, "./../__fixtures__"), + "medusa-config" + ) console.log(`Creating database ${dbName}`) await dbUtils.create(dbName) diff --git a/packages/modules/index/integration-tests/__tests__/query-builder.spec.ts b/packages/modules/index/integration-tests/__tests__/query-builder.spec.ts index b7482c466e938..b99e4180621b7 100644 --- a/packages/modules/index/integration-tests/__tests__/query-builder.spec.ts +++ b/packages/modules/index/integration-tests/__tests__/query-builder.spec.ts @@ -33,7 +33,10 @@ let medusaAppLoader!: MedusaAppLoader const beforeAll_ = async () => { try { - configLoader(path.join(__dirname, "./../__fixtures__"), "medusa-config") + await configLoader( + path.join(__dirname, "./../__fixtures__"), + "medusa-config" + ) console.log(`Creating database ${dbName}`) await dbUtils.create(dbName) diff --git a/packages/modules/locking/integration-tests/__tests__/index.spec.ts b/packages/modules/locking/integration-tests/__tests__/index.spec.ts index d6c44467e75ed..a264d95ac0f72 100644 --- a/packages/modules/locking/integration-tests/__tests__/index.spec.ts +++ b/packages/modules/locking/integration-tests/__tests__/index.spec.ts @@ -1,5 +1,5 @@ import { ILockingModule } from "@medusajs/framework/types" -import { Modules } from "@medusajs/framework/utils" +import { Modules, promiseAll } from "@medusajs/framework/utils" import { moduleIntegrationTestRunner } from "medusa-test-utils" import { setTimeout } from "node:timers/promises" @@ -63,7 +63,7 @@ moduleIntegrationTestRunner({ expect(userReleased).toBe(false) await expect(anotherUserLock).rejects.toThrowError( - `"key_name" is already locked.` + `Failed to acquire lock for key "key_name"` ) const releasing = await service.release("key_name", { @@ -82,16 +82,20 @@ moduleIntegrationTestRunner({ ownerId: "user_id_000", } - expect(service.acquire(keyToLock, user_1)).resolves.toBeUndefined() + await expect( + service.acquire(keyToLock, user_1) + ).resolves.toBeUndefined() - expect(service.acquire(keyToLock, user_1)).resolves.toBeUndefined() + await expect( + service.acquire(keyToLock, user_1) + ).resolves.toBeUndefined() - expect(service.acquire(keyToLock, user_2)).rejects.toThrowError( - `"${keyToLock}" is already locked.` + await expect(service.acquire(keyToLock, user_2)).rejects.toThrowError( + `Failed to acquire lock for key "${keyToLock}"` ) - expect(service.acquire(keyToLock, user_2)).rejects.toThrowError( - `"${keyToLock}" is already locked.` + await expect(service.acquire(keyToLock, user_2)).rejects.toThrowError( + `Failed to acquire lock for key "${keyToLock}"` ) await service.acquire(keyToLock, user_1) @@ -104,6 +108,40 @@ moduleIntegrationTestRunner({ const release = await service.release(keyToLock, user_1) expect(release).toBe(true) }) + + it("should fail to acquire the same key when no owner is provided", async () => { + const keyToLock = "mySpecialKey" + + const user_2 = { + ownerId: "user_id_000", + } + + await expect(service.acquire(keyToLock)).resolves.toBeUndefined() + + await expect(service.acquire(keyToLock)).rejects.toThrow( + `Failed to acquire lock for key "${keyToLock}"` + ) + + await expect(service.acquire(keyToLock)).rejects.toThrow( + `Failed to acquire lock for key "${keyToLock}"` + ) + + await expect(service.acquire(keyToLock, user_2)).rejects.toThrow( + `Failed to acquire lock for key "${keyToLock}"` + ) + + await expect(service.acquire(keyToLock, user_2)).rejects.toThrow( + `Failed to acquire lock for key "${keyToLock}"` + ) + + const releaseNotLocked = await service.release(keyToLock, { + ownerId: "user_id_000", + }) + expect(releaseNotLocked).toBe(false) + + const release = await service.release(keyToLock) + expect(release).toBe(true) + }) }) it("should release lock in case of failure", async () => { @@ -118,5 +156,48 @@ moduleIntegrationTestRunner({ expect(fn_1).toBeCalledTimes(1) expect(fn_2).toBeCalledTimes(1) }) + + it("should release lock in case of timeout failure", async () => { + const fn_1 = jest.fn(async () => { + await setTimeout(1010) + return "fn_1" + }) + + const fn_2 = jest.fn(async () => { + return "fn_2" + }) + + const fn_3 = jest.fn(async () => { + return "fn_3" + }) + + const ops = [ + service + .execute("lock_key", fn_1, { + timeout: 1, + }) + .catch((e) => e), + + service + .execute("lock_key", fn_2, { + timeout: 1, + }) + .catch((e) => e), + + service + .execute("lock_key", fn_3, { + timeout: 2, + }) + .catch((e) => e), + ] + + const res = await promiseAll(ops) + + expect(res).toEqual(["fn_1", Error("Timed-out acquiring lock."), "fn_3"]) + + expect(fn_1).toHaveBeenCalledTimes(1) + expect(fn_2).toHaveBeenCalledTimes(0) + expect(fn_3).toHaveBeenCalledTimes(1) + }) }, }) diff --git a/packages/modules/locking/package.json b/packages/modules/locking/package.json index a78a0bdea018b..15e20f26cb035 100644 --- a/packages/modules/locking/package.json +++ b/packages/modules/locking/package.json @@ -8,6 +8,12 @@ "url": "https://github.com/medusajs/medusa", "directory": "packages/locking" }, + "files": [ + "dist", + "!dist/**/__tests__", + "!dist/**/__mocks__", + "!dist/**/__fixtures__" + ], "publishConfig": { "access": "public" }, diff --git a/packages/modules/locking/src/index.ts b/packages/modules/locking/src/index.ts index d432f2fa58a12..86a0ba40fef18 100644 --- a/packages/modules/locking/src/index.ts +++ b/packages/modules/locking/src/index.ts @@ -1,6 +1,6 @@ import { Module, Modules } from "@medusajs/framework/utils" -import { LockingModuleService } from "@services" -import loadProviders from "./loaders/providers" +import { default as loadProviders } from "./loaders/providers" +import LockingModuleService from "./services/locking-module" export default Module(Modules.LOCKING, { service: LockingModuleService, diff --git a/packages/modules/locking/src/loaders/providers.ts b/packages/modules/locking/src/loaders/providers.ts index 7e59bb9c90837..be2b39cc5b5a4 100644 --- a/packages/modules/locking/src/loaders/providers.ts +++ b/packages/modules/locking/src/loaders/providers.ts @@ -11,19 +11,13 @@ import { LockingIdentifiersRegistrationName, LockingProviderRegistrationPrefix, } from "@types" -import { Lifetime, asFunction, asValue } from "awilix" +import { Lifetime, aliasTo, asFunction, asValue } from "awilix" import { InMemoryLockingProvider } from "../providers/in-memory" -const registrationFn = async (klass, container, pluginOptions) => { +const registrationFn = async (klass, container) => { const key = LockingProviderService.getRegistrationIdentifier(klass) - container.register({ - [LockingProviderRegistrationPrefix + key]: asFunction( - (cradle) => new klass(cradle, pluginOptions.options), - { - lifetime: klass.LIFE_TIME || Lifetime.SINGLETON, - } - ), + [LockingProviderRegistrationPrefix + key]: aliasTo("__providers__" + key), }) container.registerAdd(LockingIdentifiersRegistrationName, asValue(key)) diff --git a/packages/modules/locking/src/providers/in-memory.ts b/packages/modules/locking/src/providers/in-memory.ts index acb327bb84851..2374f8c44287a 100644 --- a/packages/modules/locking/src/providers/in-memory.ts +++ b/packages/modules/locking/src/providers/in-memory.ts @@ -38,24 +38,27 @@ export class InMemoryLockingProvider implements ILockingProvider { timeout?: number } ): Promise { - keys = Array.isArray(keys) ? keys : [keys] - - const timeoutSeconds = args?.timeout ?? 5 + const timeout = Math.max(args?.timeout ?? 5, 1) + const timeoutSeconds = Number.isNaN(timeout) ? 1 : timeout + const cancellationToken = { cancelled: false } const promises: Promise[] = [] if (timeoutSeconds > 0) { - promises.push(this.getTimeout(timeoutSeconds)) + promises.push(this.getTimeout(timeoutSeconds, cancellationToken)) } promises.push( - this.acquire(keys, { - awaitQueue: true, - }) + this.acquire_( + keys, + { + expire: timeoutSeconds, + awaitQueue: true, + }, + cancellationToken + ) ) - await Promise.race(promises).catch(async (err) => { - await this.release(keys) - }) + await Promise.race(promises) try { return await job() @@ -71,6 +74,18 @@ export class InMemoryLockingProvider implements ILockingProvider { expire?: number awaitQueue?: boolean } + ): Promise { + return this.acquire_(keys, args) + } + + async acquire_( + keys: string | string[], + args?: { + ownerId?: string | null + expire?: number + awaitQueue?: boolean + }, + cancellationToken?: { cancelled: boolean } ): Promise { keys = Array.isArray(keys) ? keys : [keys] const { ownerId, expire } = args ?? {} @@ -100,7 +115,7 @@ export class InMemoryLockingProvider implements ILockingProvider { continue } - if (lock.ownerId === ownerId) { + if (lock.ownerId !== null && lock.ownerId === ownerId) { if (expire) { lock.expiration = now + expire * 1000 this.locks.set(key, lock) @@ -111,10 +126,14 @@ export class InMemoryLockingProvider implements ILockingProvider { if (lock.currentPromise && args?.awaitQueue) { await lock.currentPromise.promise + if (cancellationToken?.cancelled) { + return + } + return this.acquire(keys, args) } - throw new Error(`"${key}" is already locked.`) + throw new Error(`Failed to acquire lock for key "${key}"`) } } @@ -166,9 +185,13 @@ export class InMemoryLockingProvider implements ILockingProvider { } } - private async getTimeout(seconds: number): Promise { + private async getTimeout( + seconds: number, + cancellationToken: { cancelled: boolean } + ): Promise { return new Promise((_, reject) => { setTimeout(() => { + cancellationToken.cancelled = true reject(new Error("Timed-out acquiring lock.")) }, seconds * 1000) }) diff --git a/packages/modules/providers/auth-emailpass/src/index.ts b/packages/modules/providers/auth-emailpass/src/index.ts index 1b5c666fb3054..02f35016ebe65 100644 --- a/packages/modules/providers/auth-emailpass/src/index.ts +++ b/packages/modules/providers/auth-emailpass/src/index.ts @@ -1,10 +1,8 @@ -import { ModuleProviderExports } from "@medusajs/framework/types" +import { ModuleProvider, Modules } from "@medusajs/framework/utils" import { EmailPassAuthService } from "./services/emailpass" const services = [EmailPassAuthService] -const providerExport: ModuleProviderExports = { +export default ModuleProvider(Modules.AUTH, { services, -} - -export default providerExport +}) diff --git a/packages/modules/providers/auth-github/src/index.ts b/packages/modules/providers/auth-github/src/index.ts index 70fde0eefd0b8..190e1639db755 100644 --- a/packages/modules/providers/auth-github/src/index.ts +++ b/packages/modules/providers/auth-github/src/index.ts @@ -1,10 +1,8 @@ -import { ModuleProviderExports } from "@medusajs/framework/types" +import { ModuleProvider, Modules } from "@medusajs/framework/utils" import { GithubAuthService } from "./services/github" const services = [GithubAuthService] -const providerExport: ModuleProviderExports = { +export default ModuleProvider(Modules.AUTH, { services, -} - -export default providerExport +}) diff --git a/packages/modules/providers/auth-google/src/index.ts b/packages/modules/providers/auth-google/src/index.ts index 9245ab87564c1..4cea9bfbe5a32 100644 --- a/packages/modules/providers/auth-google/src/index.ts +++ b/packages/modules/providers/auth-google/src/index.ts @@ -1,10 +1,8 @@ -import { ModuleProviderExports } from "@medusajs/framework/types" +import { ModuleProvider, Modules } from "@medusajs/framework/utils" import { GoogleAuthService } from "./services/google" const services = [GoogleAuthService] -const providerExport: ModuleProviderExports = { +export default ModuleProvider(Modules.AUTH, { services, -} - -export default providerExport +}) diff --git a/packages/modules/providers/file-local/src/index.ts b/packages/modules/providers/file-local/src/index.ts index 92f1b8dbad2e6..2803b6c867c0f 100644 --- a/packages/modules/providers/file-local/src/index.ts +++ b/packages/modules/providers/file-local/src/index.ts @@ -1,10 +1,8 @@ -import { ModuleProviderExports } from "@medusajs/framework/types" +import { ModuleProvider, Modules } from "@medusajs/framework/utils" import { LocalFileService } from "./services/local-file" const services = [LocalFileService] -const providerExport: ModuleProviderExports = { +export default ModuleProvider(Modules.FILE, { services, -} - -export default providerExport +}) diff --git a/packages/modules/providers/file-s3/src/index.ts b/packages/modules/providers/file-s3/src/index.ts index 741be94297089..4851232c6fd80 100644 --- a/packages/modules/providers/file-s3/src/index.ts +++ b/packages/modules/providers/file-s3/src/index.ts @@ -1,10 +1,8 @@ -import { ModuleProviderExports } from "@medusajs/framework/types" +import { ModuleProvider, Modules } from "@medusajs/framework/utils" import { S3FileService } from "./services/s3-file" const services = [S3FileService] -const providerExport: ModuleProviderExports = { +export default ModuleProvider(Modules.FILE, { services, -} - -export default providerExport +}) diff --git a/packages/modules/providers/fulfillment-manual/src/index.ts b/packages/modules/providers/fulfillment-manual/src/index.ts index f7652183da504..93b20068a8207 100644 --- a/packages/modules/providers/fulfillment-manual/src/index.ts +++ b/packages/modules/providers/fulfillment-manual/src/index.ts @@ -1,10 +1,8 @@ -import { ModuleProviderExports } from "@medusajs/framework/types" +import { ModuleProvider, Modules } from "@medusajs/framework/utils" import { ManualFulfillmentService } from "./services/manual-fulfillment" const services = [ManualFulfillmentService] -const providerExport: ModuleProviderExports = { +export default ModuleProvider(Modules.FULFILLMENT, { services, -} - -export default providerExport +}) diff --git a/packages/modules/providers/locking-redis/.gitignore b/packages/modules/providers/locking-redis/.gitignore new file mode 100644 index 0000000000000..83cb36a41ea17 --- /dev/null +++ b/packages/modules/providers/locking-redis/.gitignore @@ -0,0 +1,4 @@ +dist +node_modules +.DS_store +yarn.lock diff --git a/packages/modules/providers/locking-redis/integration-tests/__tests__/index.spec.ts b/packages/modules/providers/locking-redis/integration-tests/__tests__/index.spec.ts new file mode 100644 index 0000000000000..960282039af05 --- /dev/null +++ b/packages/modules/providers/locking-redis/integration-tests/__tests__/index.spec.ts @@ -0,0 +1,220 @@ +import { ILockingModule } from "@medusajs/framework/types" +import { Modules, promiseAll } from "@medusajs/framework/utils" +import { moduleIntegrationTestRunner } from "medusa-test-utils" +import { setTimeout } from "node:timers/promises" + +jest.setTimeout(5000) + +const providerId = "locking-redis" +moduleIntegrationTestRunner({ + moduleName: Modules.LOCKING, + moduleOptions: { + providers: [ + { + id: providerId, + resolve: require.resolve("../../src"), + is_default: true, + options: { + redisUrl: process.env.REDIS_URL ?? "redis://localhost:6379", + }, + }, + ], + }, + testSuite: ({ service }) => { + describe("Locking Module Service", () => { + let stock = 5 + function replenishStock() { + stock = 5 + } + function hasStock() { + return stock > 0 + } + async function reduceStock() { + await setTimeout(10) + stock-- + } + async function buy() { + if (hasStock()) { + await reduceStock() + return true + } + return false + } + + beforeEach(async () => { + await service.releaseAll() + }) + + it("should execute functions respecting the key locked", async () => { + // 10 parallel calls to buy should oversell the stock + const prom: any[] = [] + for (let i = 0; i < 10; i++) { + prom.push(buy()) + } + await Promise.all(prom) + expect(stock).toBe(-5) + + replenishStock() + + // 10 parallel calls to buy with lock should not oversell the stock + const promWLock: any[] = [] + for (let i = 0; i < 10; i++) { + promWLock.push(service.execute("item_1", buy)) + } + await Promise.all(promWLock) + + expect(stock).toBe(0) + }) + + it("should acquire lock and release it", async () => { + await service.acquire("key_name", { + ownerId: "user_id_123", + }) + + const userReleased = await service.release("key_name", { + ownerId: "user_id_456", + }) + const anotherUserLock = service.acquire("key_name", { + ownerId: "user_id_456", + }) + + expect(userReleased).toBe(false) + await expect(anotherUserLock).rejects.toThrow( + `Failed to acquire lock for key "key_name"` + ) + + const releasing = await service.release("key_name", { + ownerId: "user_id_123", + }) + + expect(releasing).toBe(true) + }) + + it("should acquire lock and release it during parallel calls", async () => { + const keyToLock = "mySpecialKey" + const user_1 = { + ownerId: "user_id_456", + } + const user_2 = { + ownerId: "user_id_000", + } + + await expect( + service.acquire(keyToLock, user_1) + ).resolves.toBeUndefined() + + await expect( + service.acquire(keyToLock, user_1) + ).resolves.toBeUndefined() + + await expect(service.acquire(keyToLock, user_2)).rejects.toThrow( + `Failed to acquire lock for key "${keyToLock}"` + ) + + await expect(service.acquire(keyToLock, user_2)).rejects.toThrow( + `Failed to acquire lock for key "${keyToLock}"` + ) + + await service.acquire(keyToLock, user_1) + + const releaseNotLocked = await service.release(keyToLock, { + ownerId: "user_id_000", + }) + expect(releaseNotLocked).toBe(false) + + const release = await service.release(keyToLock, user_1) + expect(release).toBe(true) + }) + + it("should fail to acquire the same key when no owner is provided", async () => { + const keyToLock = "mySpecialKey" + + const user_2 = { + ownerId: "user_id_000", + } + + await expect(service.acquire(keyToLock)).resolves.toBeUndefined() + + await expect(service.acquire(keyToLock)).rejects.toThrow( + `Failed to acquire lock for key "${keyToLock}"` + ) + + await expect(service.acquire(keyToLock)).rejects.toThrow( + `Failed to acquire lock for key "${keyToLock}"` + ) + + await expect(service.acquire(keyToLock, user_2)).rejects.toThrow( + `Failed to acquire lock for key "${keyToLock}"` + ) + + await expect(service.acquire(keyToLock, user_2)).rejects.toThrow( + `Failed to acquire lock for key "${keyToLock}"` + ) + + const releaseNotLocked = await service.release(keyToLock, { + ownerId: "user_id_000", + }) + expect(releaseNotLocked).toBe(false) + + const release = await service.release(keyToLock) + expect(release).toBe(true) + }) + }) + + it("should release lock in case of failure", async () => { + const fn_1 = jest.fn(async () => { + throw new Error("Error") + }) + const fn_2 = jest.fn(async () => {}) + + await service.execute("lock_key", fn_1).catch(() => {}) + await service.execute("lock_key", fn_2).catch(() => {}) + + expect(fn_1).toHaveBeenCalledTimes(1) + expect(fn_2).toHaveBeenCalledTimes(1) + }) + + it("should release lock in case of timeout failure", async () => { + const fn_1 = jest.fn(async () => { + await setTimeout(1010) + return "fn_1" + }) + + const fn_2 = jest.fn(async () => { + return "fn_2" + }) + + const fn_3 = jest.fn(async () => { + return "fn_3" + }) + + const ops = [ + service + .execute("lock_key", fn_1, { + timeout: 1, + }) + .catch((e) => e), + + service + .execute("lock_key", fn_2, { + timeout: 1, + }) + .catch((e) => e), + + service + .execute("lock_key", fn_3, { + timeout: 5, + }) + .catch((e) => e), + ] + + const res = await promiseAll(ops) + + expect(res).toEqual(["fn_1", Error("Timed-out acquiring lock."), "fn_3"]) + + expect(fn_1).toHaveBeenCalledTimes(1) + expect(fn_2).toHaveBeenCalledTimes(0) + expect(fn_3).toHaveBeenCalledTimes(1) + }) + }, +}) diff --git a/packages/modules/providers/locking-redis/jest.config.js b/packages/modules/providers/locking-redis/jest.config.js new file mode 100644 index 0000000000000..818699559a62f --- /dev/null +++ b/packages/modules/providers/locking-redis/jest.config.js @@ -0,0 +1,10 @@ +const defineJestConfig = require("../../../../define_jest_config") +module.exports = defineJestConfig({ + moduleNameMapper: { + "^@models": "/src/models", + "^@services": "/src/services", + "^@repositories": "/src/repositories", + "^@types": "/src/types", + "^@utils": "/src/utils", + }, +}) diff --git a/packages/modules/providers/locking-redis/package.json b/packages/modules/providers/locking-redis/package.json new file mode 100644 index 0000000000000..ae05355d091f2 --- /dev/null +++ b/packages/modules/providers/locking-redis/package.json @@ -0,0 +1,48 @@ +{ + "name": "@medusajs/locking-redis", + "version": "0.0.1", + "description": "Redis Lock for Medusa", + "main": "dist/index.js", + "repository": { + "type": "git", + "url": "https://github.com/medusajs/medusa", + "directory": "packages/locking-redis" + }, + "files": [ + "dist", + "!dist/**/__tests__", + "!dist/**/__mocks__", + "!dist/**/__fixtures__" + ], + "engines": { + "node": ">=20" + }, + "author": "Medusa", + "license": "MIT", + "devDependencies": { + "@medusajs/framework": "^0.0.1", + "@swc/core": "^1.7.28", + "@swc/jest": "^0.2.36", + "jest": "^29.7.0", + "rimraf": "^5.0.1", + "typescript": "^5.6.2" + }, + "peerDependencies": { + "@medusajs/framework": "^0.0.1" + }, + "dependencies": { + "ioredis": "^5.4.1" + }, + "scripts": { + "watch": "tsc --build --watch", + "watch:test": "tsc --build tsconfig.spec.json --watch", + "resolve:aliases": "tsc --showConfig -p tsconfig.json > tsconfig.resolved.json && tsc-alias -p tsconfig.resolved.json && rimraf tsconfig.resolved.json", + "build": "rimraf dist && tsc --build && npm run resolve:aliases", + "test": "jest --passWithNoTests src", + "test:integration": "jest --runInBand --forceExit -- integration-tests/**/__tests__/**/*.spec.ts" + }, + "keywords": [ + "medusa-providers", + "medusa-providers-locking" + ] +} diff --git a/packages/modules/providers/locking-redis/src/index.ts b/packages/modules/providers/locking-redis/src/index.ts new file mode 100644 index 0000000000000..ef5860a634478 --- /dev/null +++ b/packages/modules/providers/locking-redis/src/index.ts @@ -0,0 +1,11 @@ +import { ModuleProvider, Modules } from "@medusajs/framework/utils" +import Loader from "./loaders" +import { RedisLockingProvider } from "./services/redis-lock" + +const services = [RedisLockingProvider] +const loaders = [Loader] + +export default ModuleProvider(Modules.LOCKING, { + services, + loaders, +}) diff --git a/packages/modules/providers/locking-redis/src/loaders/index.ts b/packages/modules/providers/locking-redis/src/loaders/index.ts new file mode 100644 index 0000000000000..5f8ad5ce12b88 --- /dev/null +++ b/packages/modules/providers/locking-redis/src/loaders/index.ts @@ -0,0 +1,41 @@ +import { Modules } from "@medusajs/framework/utils" +import { ProviderLoaderOptions } from "@medusajs/types" +import { RedisCacheModuleOptions } from "@types" +import { asValue } from "awilix" +import Redis from "ioredis" + +export default async ({ + container, + logger, + options, + moduleOptions, +}: ProviderLoaderOptions): Promise => { + const { redisUrl, redisOptions, namespace } = + options as RedisCacheModuleOptions + + if (!redisUrl) { + throw Error( + `No "redisUrl" provided in "${Modules.LOCKING}" module, "locking-redis" provider options. It is required for the "locking-redis" Module provider.` + ) + } + + const connection = new Redis(redisUrl, { + // Lazy connect to properly handle connection errors + lazyConnect: true, + ...(redisOptions ?? {}), + }) + + try { + await connection.connect() + logger?.info(`Connection to Redis in "locking-redis" provider established`) + } catch (err) { + logger?.error( + `An error occurred while connecting to Redis in provider "locking-redis": ${err}` + ) + } + + container.register({ + redisClient: asValue(connection), + prefix: asValue(namespace ?? "medusa_lock:"), + }) +} diff --git a/packages/modules/providers/locking-redis/src/services/redis-lock.ts b/packages/modules/providers/locking-redis/src/services/redis-lock.ts new file mode 100644 index 0000000000000..81c1e365176d4 --- /dev/null +++ b/packages/modules/providers/locking-redis/src/services/redis-lock.ts @@ -0,0 +1,280 @@ +import { promiseAll } from "@medusajs/framework/utils" +import { ILockingProvider } from "@medusajs/types" +import { RedisCacheModuleOptions } from "@types" +import { Redis } from "ioredis" +import { setTimeout } from "node:timers/promises" + +export class RedisLockingProvider implements ILockingProvider { + static identifier = "locking-redis" + + protected redisClient: Redis & { + acquireLock: ( + key: string, + ownerId: string, + ttl: number, + awaitQueue?: boolean + ) => Promise + releaseLock: (key: string, ownerId: string) => Promise + } + protected keyNamePrefix: string + protected waitLockingTimeout: number = 5 + protected defaultRetryInterval: number = 5 + protected maximumRetryInterval: number = 200 + + constructor({ redisClient, prefix }, options: RedisCacheModuleOptions) { + this.redisClient = redisClient + this.keyNamePrefix = prefix ?? "medusa_lock:" + + if (!isNaN(+options?.waitLockingTimeout!)) { + this.waitLockingTimeout = +options.waitLockingTimeout! + } + + if (!isNaN(+options?.defaultRetryInterval!)) { + this.defaultRetryInterval = +options.defaultRetryInterval! + } + + if (!isNaN(+options?.maximumRetryInterval!)) { + this.maximumRetryInterval = +options.maximumRetryInterval! + } + + // Define the custom command for acquiring locks + this.redisClient.defineCommand("acquireLock", { + numberOfKeys: 1, + lua: ` + local key = KEYS[1] + local ownerId = ARGV[1] + local ttl = tonumber(ARGV[2]) + local awaitQueue = ARGV[3] == 'true' + + local setArgs = {key, ownerId, 'NX'} + if ttl > 0 then + table.insert(setArgs, 'EX') + table.insert(setArgs, ttl) + end + + local setResult = redis.call('SET', unpack(setArgs)) + + if setResult then + return 1 + elseif not awaitQueue then + -- Key already exists; retrieve the current ownerId + local currentOwnerId = redis.call('GET', key) + if currentOwnerId == '*' then + return 0 + elseif currentOwnerId == ownerId then + setArgs = {key, ownerId, 'XX'} + if ttl > 0 then + table.insert(setArgs, 'EX') + table.insert(setArgs, ttl) + end + redis.call('SET', unpack(setArgs)) + return 1 + else + return 0 + end + else + return 0 + end + + `, + }) + + // Define the custom command for releasing locks + this.redisClient.defineCommand("releaseLock", { + numberOfKeys: 1, + lua: ` + local key = KEYS[1] + local ownerId = ARGV[1] + + if redis.call('GET', key) == ownerId then + return redis.call('DEL', key) + else + return 0 + end + `, + }) + } + + private getKeyName(key: string): string { + return `${this.keyNamePrefix}${key}` + } + + async execute( + keys: string | string[], + job: () => Promise, + args?: { + timeout?: number + } + ): Promise { + const timeout = Math.max(args?.timeout ?? this.waitLockingTimeout, 1) + const timeoutSeconds = Number.isNaN(timeout) ? 1 : timeout + + const cancellationToken = { cancelled: false } + const promises: Promise[] = [] + if (timeoutSeconds > 0) { + promises.push(this.getTimeout(timeoutSeconds, cancellationToken)) + } + + promises.push( + this.acquire_( + keys, + { + awaitQueue: true, + expire: args?.timeout ? timeoutSeconds : 0, + }, + cancellationToken + ) + ) + + await Promise.race(promises) + + try { + return await job() + } finally { + await this.release(keys) + } + } + + async acquire( + keys: string | string[], + args?: { + ownerId?: string + expire?: number + awaitQueue?: boolean + } + ): Promise { + return this.acquire_(keys, args) + } + + async acquire_( + keys: string | string[], + args?: { + ownerId?: string + expire?: number + awaitQueue?: boolean + }, + cancellationToken?: { cancelled: boolean } + ): Promise { + keys = Array.isArray(keys) ? keys : [keys] + + const timeout = Math.max(args?.expire ?? this.waitLockingTimeout, 1) + const timeoutSeconds = Number.isNaN(timeout) ? 1 : timeout + let retryTimes = 0 + + const ownerId = args?.ownerId ?? "*" + const awaitQueue = args?.awaitQueue ?? false + + const acquirePromises = keys.map(async (key) => { + const errMessage = `Failed to acquire lock for key "${key}"` + const keyName = this.getKeyName(key) + + const acquireLock = async () => { + while (true) { + if (cancellationToken?.cancelled) { + throw new Error(errMessage) + } + + const result = await this.redisClient.acquireLock( + keyName, + ownerId, + args?.expire ? timeoutSeconds : 0, + awaitQueue + ) + + if (result === 1) { + break + } else { + if (awaitQueue) { + // Wait for a short period before retrying + await setTimeout( + Math.min( + this.defaultRetryInterval + + (retryTimes / 10) * this.defaultRetryInterval, + this.maximumRetryInterval + ) + ) + retryTimes++ + } else { + throw new Error(errMessage) + } + } + } + } + + await acquireLock() + }) + + await promiseAll(acquirePromises) + } + + async release( + keys: string | string[], + args?: { + ownerId?: string | null + } + ): Promise { + const ownerId = args?.ownerId ?? "*" + keys = Array.isArray(keys) ? keys : [keys] + + const releasePromises = keys.map(async (key) => { + const keyName = this.getKeyName(key) + const result = await this.redisClient.releaseLock(keyName, ownerId) + return result === 1 + }) + + const results = await promiseAll(releasePromises) + + return results.every((released) => released) + } + + async releaseAll(args?: { ownerId?: string | null }): Promise { + const ownerId = args?.ownerId ?? "*" + + const pattern = `${this.keyNamePrefix}*` + let cursor = "0" + + do { + const result = await this.redisClient.scan( + cursor, + "MATCH", + pattern, + "COUNT", + 100 + ) + cursor = result[0] + const keys = result[1] + + if (keys.length > 0) { + const pipeline = this.redisClient.pipeline() + + keys.forEach((key) => { + pipeline.get(key) + }) + + const currentOwners = await pipeline.exec() + + const deletePipeline = this.redisClient.pipeline() + keys.forEach((key, idx) => { + const currentOwner = currentOwners?.[idx]?.[1] + + if (currentOwner === ownerId) { + deletePipeline.del(key) + } + }) + + await deletePipeline.exec() + } + } while (cursor !== "0") + } + + private async getTimeout( + seconds: number, + cancellationToken: { cancelled: boolean } + ): Promise { + return new Promise(async (_, reject) => { + await setTimeout(seconds * 1000) + cancellationToken.cancelled = true + reject(new Error("Timed-out acquiring lock.")) + }) + } +} diff --git a/packages/modules/providers/locking-redis/src/types/index.ts b/packages/modules/providers/locking-redis/src/types/index.ts new file mode 100644 index 0000000000000..702e0df1ec204 --- /dev/null +++ b/packages/modules/providers/locking-redis/src/types/index.ts @@ -0,0 +1,45 @@ +import { RedisOptions } from "ioredis" + +/** + * Module config type + */ +export type RedisCacheModuleOptions = { + /** + * Time to keep data in cache (in seconds) + */ + ttl?: number + + /** + * Redis connection string + */ + redisUrl?: string + + /** + * Redis client options + */ + redisOptions?: RedisOptions + + /** + * Prefix for event keys + * @default `medusa_lock:` + */ + namespace?: string + + /** + * Time to wait for lock (in seconds) + * @default 5 + */ + waitLockingTimeout?: number + + /** + * Default retry interval (in milliseconds) + * @default 5 + */ + defaultRetryInterval?: number + + /** + * Maximum retry interval (in milliseconds) + * @default 200 + */ + maximumRetryInterval?: number +} diff --git a/packages/modules/providers/locking-redis/tsconfig.json b/packages/modules/providers/locking-redis/tsconfig.json new file mode 100644 index 0000000000000..90f3a70b383ef --- /dev/null +++ b/packages/modules/providers/locking-redis/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../../../_tsconfig.base.json", + "compilerOptions": { + "paths": { + "@models": ["./src/models"], + "@services": ["./src/services"], + "@repositories": ["./src/repositories"], + "@types": ["./src/types"], + "@utils": ["./src/utils"] + } + } +} diff --git a/packages/modules/providers/notification-local/src/index.ts b/packages/modules/providers/notification-local/src/index.ts index c0efc3f3e7409..7695781064fcb 100644 --- a/packages/modules/providers/notification-local/src/index.ts +++ b/packages/modules/providers/notification-local/src/index.ts @@ -1,10 +1,8 @@ -import { ModuleProviderExports } from "@medusajs/framework/types" +import { ModuleProvider, Modules } from "@medusajs/framework/utils" import { LocalNotificationService } from "./services/local" const services = [LocalNotificationService] -const providerExport: ModuleProviderExports = { +export default ModuleProvider(Modules.NOTIFICATION, { services, -} - -export default providerExport +}) diff --git a/packages/modules/providers/notification-sendgrid/src/index.ts b/packages/modules/providers/notification-sendgrid/src/index.ts index f49098cb2dc8a..f083881d10b12 100644 --- a/packages/modules/providers/notification-sendgrid/src/index.ts +++ b/packages/modules/providers/notification-sendgrid/src/index.ts @@ -1,10 +1,8 @@ -import { ModuleProviderExports } from "@medusajs/framework/types" +import { ModuleProvider, Modules } from "@medusajs/framework/utils" import { SendgridNotificationService } from "./services/sendgrid" const services = [SendgridNotificationService] -const providerExport: ModuleProviderExports = { +export default ModuleProvider(Modules.NOTIFICATION, { services, -} - -export default providerExport +}) diff --git a/packages/modules/providers/payment-stripe/src/index.ts b/packages/modules/providers/payment-stripe/src/index.ts index 5d7614763e44c..771c6b2b53e9e 100644 --- a/packages/modules/providers/payment-stripe/src/index.ts +++ b/packages/modules/providers/payment-stripe/src/index.ts @@ -1,4 +1,4 @@ -import { ModuleProviderExports } from "@medusajs/framework/types" +import { ModuleProvider, Modules } from "@medusajs/framework/utils" import { StripeBancontactService, StripeBlikService, @@ -17,8 +17,6 @@ const services = [ StripePrzelewy24Service, ] -const providerExport: ModuleProviderExports = { +export default ModuleProvider(Modules.PAYMENT, { services, -} - -export default providerExport +}) diff --git a/www/apps/book/app/advanced-development/modules/options/page.mdx b/www/apps/book/app/advanced-development/modules/options/page.mdx index 78289eb80dbc1..3575ab0e21acc 100644 --- a/www/apps/book/app/advanced-development/modules/options/page.mdx +++ b/www/apps/book/app/advanced-development/modules/options/page.mdx @@ -25,7 +25,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "./modules/hello", + resolve: "./src/modules/hello", options: { capitalize: true, }, diff --git a/www/apps/book/app/basics/modules/page.mdx b/www/apps/book/app/basics/modules/page.mdx index 2dca6f60a1b50..c1356e985bffa 100644 --- a/www/apps/book/app/basics/modules/page.mdx +++ b/www/apps/book/app/basics/modules/page.mdx @@ -134,13 +134,13 @@ module.exports = defineConfig({ }, modules: [ { - resolve: "./modules/hello", + resolve: "./src/modules/hello", } ] }) ``` -Its value is an array of objects, each having a `resolve` property, whose value is either a path to module's directory relative to `src` (it shouldn't include `src` in the path), or an `npm` package’s name. +Its value is an array of objects, each having a `resolve` property, whose value is either a path to module's directory, or an `npm` package’s name. ### 5. Generate Migrations diff --git a/www/apps/book/app/customization/custom-features/module/page.mdx b/www/apps/book/app/customization/custom-features/module/page.mdx index ae3927d7642e5..9dc8ac4f2d3f4 100644 --- a/www/apps/book/app/customization/custom-features/module/page.mdx +++ b/www/apps/book/app/customization/custom-features/module/page.mdx @@ -104,7 +104,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "./modules/brand", + resolve: "./src/modules/brand", } ] }) diff --git a/www/apps/book/app/customization/integrate-systems/service/page.mdx b/www/apps/book/app/customization/integrate-systems/service/page.mdx index 0cbb8a587725d..cd79fa6c9d83f 100644 --- a/www/apps/book/app/customization/integrate-systems/service/page.mdx +++ b/www/apps/book/app/customization/integrate-systems/service/page.mdx @@ -187,7 +187,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "./modules/brand", + resolve: "./src/modules/brand", options: { apiKey: process.env.BRAND_API_KEY || "temp", }, diff --git a/www/apps/book/app/debugging-and-testing/testing-tools/modules-tests/module-example/page.mdx b/www/apps/book/app/debugging-and-testing/testing-tools/modules-tests/module-example/page.mdx index 713a5337a3870..48f828aa2ac11 100644 --- a/www/apps/book/app/debugging-and-testing/testing-tools/modules-tests/module-example/page.mdx +++ b/www/apps/book/app/debugging-and-testing/testing-tools/modules-tests/module-example/page.mdx @@ -47,7 +47,7 @@ import MyCustom from "../models/my-custom" moduleIntegrationTestRunner({ moduleName: HELLO_MODULE, moduleModels: [MyCustom], - resolve: "./modules/hello", + resolve: "./src/modules/hello", testSuite: ({ service }) => { describe("HelloModuleService", () => { it("says hello world", () => { diff --git a/www/apps/book/app/debugging-and-testing/testing-tools/modules-tests/page.mdx b/www/apps/book/app/debugging-and-testing/testing-tools/modules-tests/page.mdx index b116eca15bea5..4464c0f0d5276 100644 --- a/www/apps/book/app/debugging-and-testing/testing-tools/modules-tests/page.mdx +++ b/www/apps/book/app/debugging-and-testing/testing-tools/modules-tests/page.mdx @@ -32,7 +32,7 @@ import MyCustom from "../models/my-custom" moduleIntegrationTestRunner({ moduleName: HELLO_MODULE, moduleModels: [MyCustom], - resolve: "./modules/hello", + resolve: "./src/modules/hello", testSuite: ({ service }) => { // TODO write tests }, @@ -43,7 +43,7 @@ The `moduleIntegrationTestRunner` function accepts as a parameter an object with - `moduleName`: The name of the module. - `moduleModels`: An array of models in the module. Refer to [this section](#write-tests-for-modules-without-data-models) if your module doesn't have data models. -- `resolve`: The path to the model relative to the `src` directory. +- `resolve`: The path to the model. - `testSuite`: A function that defines the tests to run. The `testSuite` function accepts as a parameter an object having the `service` property, which is an instance of the module's main service. diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs index d7b0ee26d522c..97fb67ab60289 100644 --- a/www/apps/book/generated/edit-dates.mjs +++ b/www/apps/book/generated/edit-dates.mjs @@ -44,7 +44,7 @@ export const generatedEditDates = { "app/advanced-development/data-models/write-migration/page.mdx": "2024-07-15T17:46:10+02:00", "app/advanced-development/data-models/manage-relationships/page.mdx": "2024-09-10T11:39:51.167Z", "app/advanced-development/modules/remote-query/page.mdx": "2024-07-21T21:20:24+02:00", - "app/advanced-development/modules/options/page.mdx": "2024-09-30T08:43:53.126Z", + "app/advanced-development/modules/options/page.mdx": "2024-10-16T08:49:27.162Z", "app/advanced-development/data-models/relationships/page.mdx": "2024-09-30T08:43:53.125Z", "app/advanced-development/workflows/compensation-function/page.mdx": "2024-09-30T08:43:53.128Z", "app/advanced-development/modules/service-factory/page.mdx": "2024-09-30T08:43:53.127Z", @@ -75,8 +75,8 @@ export const generatedEditDates = { "app/advanced-development/api-routes/validation/page.mdx": "2024-09-11T10:46:31.476Z", "app/advanced-development/api-routes/errors/page.mdx": "2024-09-30T08:43:53.121Z", "app/advanced-development/admin/constraints/page.mdx": "2024-09-10T11:39:51.165Z", - "app/debugging-and-testing/testing-tools/modules-tests/module-example/page.mdx": "2024-09-30T08:43:53.139Z", - "app/debugging-and-testing/testing-tools/modules-tests/page.mdx": "2024-09-30T08:43:53.139Z", + "app/debugging-and-testing/testing-tools/modules-tests/module-example/page.mdx": "2024-10-16T08:50:03.061Z", + "app/debugging-and-testing/testing-tools/modules-tests/page.mdx": "2024-10-16T08:50:23.232Z", "app/advanced-development/module-links/custom-columns/page.mdx": "2024-09-16T15:51:33.570Z", "app/advanced-development/module-links/directions/page.mdx": "2024-09-16T15:37:51.441Z", "app/advanced-development/module-links/page.mdx": "2024-09-16T15:36:48.190Z", @@ -90,7 +90,7 @@ export const generatedEditDates = { "app/advanced-development/workflows/page.mdx": "2024-09-18T08:00:57.364Z", "app/advanced-development/workflows/variable-manipulation/page.mdx": "2024-09-30T08:43:53.130Z", "app/customization/custom-features/api-route/page.mdx": "2024-09-12T12:42:34.201Z", - "app/customization/custom-features/module/page.mdx": "2024-10-03T13:03:41.359Z", + "app/customization/custom-features/module/page.mdx": "2024-10-16T08:49:44.676Z", "app/customization/custom-features/workflow/page.mdx": "2024-09-30T08:43:53.133Z", "app/customization/extend-models/create-links/page.mdx": "2024-09-30T08:43:53.133Z", "app/customization/extend-models/extend-create-product/page.mdx": "2024-09-30T08:43:53.134Z", @@ -104,7 +104,7 @@ export const generatedEditDates = { "app/customization/integrate-systems/handle-event/page.mdx": "2024-09-30T08:43:53.135Z", "app/customization/integrate-systems/page.mdx": "2024-09-12T12:33:29.827Z", "app/customization/integrate-systems/schedule-task/page.mdx": "2024-09-30T08:43:53.135Z", - "app/customization/integrate-systems/service/page.mdx": "2024-09-30T08:43:53.135Z", + "app/customization/integrate-systems/service/page.mdx": "2024-10-16T08:49:50.899Z", "app/customization/next-steps/page.mdx": "2024-09-12T10:50:04.873Z", "app/customization/page.mdx": "2024-09-12T11:16:18.504Z", "app/more-resources/cheatsheet/page.mdx": "2024-07-11T16:11:26.480Z", @@ -113,5 +113,5 @@ export const generatedEditDates = { "app/architecture/overview/page.mdx": "2024-09-23T12:55:01.339Z", "app/advanced-development/data-models/infer-type/page.mdx": "2024-09-30T08:43:53.123Z", "app/advanced-development/custom-cli-scripts/seed-data/page.mdx": "2024-10-03T11:11:07.181Z", - "app/basics/modules/page.mdx": "2024-10-03T13:05:49.551Z" + "app/basics/modules/page.mdx": "2024-10-16T08:49:39.997Z" } \ No newline at end of file diff --git a/www/apps/resources/app/architectural-modules/cache/create/page.mdx b/www/apps/resources/app/architectural-modules/cache/create/page.mdx index 73bb0d10d3be1..8c26df2de2bc7 100644 --- a/www/apps/resources/app/architectural-modules/cache/create/page.mdx +++ b/www/apps/resources/app/architectural-modules/cache/create/page.mdx @@ -161,7 +161,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "./modules/my-cache", + resolve: "./src/modules/my-cache", options: { // any options ttl: 30, diff --git a/www/apps/resources/app/architectural-modules/cache/in-memory/page.mdx b/www/apps/resources/app/architectural-modules/cache/in-memory/page.mdx index 47ff9e87dada2..38d9585316469 100644 --- a/www/apps/resources/app/architectural-modules/cache/in-memory/page.mdx +++ b/www/apps/resources/app/architectural-modules/cache/in-memory/page.mdx @@ -32,7 +32,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/cache-inmemory", + resolve: "@medusajs/medusa/cache-inmemory", options: { // optional options }, diff --git a/www/apps/resources/app/architectural-modules/cache/redis/page.mdx b/www/apps/resources/app/architectural-modules/cache/redis/page.mdx index d82de35781723..8f3b06a19750f 100644 --- a/www/apps/resources/app/architectural-modules/cache/redis/page.mdx +++ b/www/apps/resources/app/architectural-modules/cache/redis/page.mdx @@ -34,7 +34,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/cache-redis", + resolve: "@medusajs/medusa/cache-redis", options: { redisUrl: process.env.CACHE_REDIS_URL, }, diff --git a/www/apps/resources/app/architectural-modules/event/create/page.mdx b/www/apps/resources/app/architectural-modules/event/create/page.mdx index c34b9e1e83e37..9344ee3f490fd 100644 --- a/www/apps/resources/app/architectural-modules/event/create/page.mdx +++ b/www/apps/resources/app/architectural-modules/event/create/page.mdx @@ -116,7 +116,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "./modules/my-event", + resolve: "./src/modules/my-event", options: { // any options }, diff --git a/www/apps/resources/app/architectural-modules/event/local/page.mdx b/www/apps/resources/app/architectural-modules/event/local/page.mdx index 9cf554a96a14a..e3d31a90928c3 100644 --- a/www/apps/resources/app/architectural-modules/event/local/page.mdx +++ b/www/apps/resources/app/architectural-modules/event/local/page.mdx @@ -31,7 +31,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/event-bus-local" + resolve: "@medusajs/medusa/event-bus-local" } ] }) diff --git a/www/apps/resources/app/architectural-modules/event/redis/page.mdx b/www/apps/resources/app/architectural-modules/event/redis/page.mdx index 45feabb361ab1..584f9dda7b72c 100644 --- a/www/apps/resources/app/architectural-modules/event/redis/page.mdx +++ b/www/apps/resources/app/architectural-modules/event/redis/page.mdx @@ -38,7 +38,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/event-bus-redis", + resolve: "@medusajs/medusa/event-bus-redis", options: { redisUrl: process.env.EVENTS_REDIS_URL, }, diff --git a/www/apps/resources/app/architectural-modules/file/local/page.mdx b/www/apps/resources/app/architectural-modules/file/local/page.mdx index 33d4a9e25c574..9ff2171657224 100644 --- a/www/apps/resources/app/architectural-modules/file/local/page.mdx +++ b/www/apps/resources/app/architectural-modules/file/local/page.mdx @@ -41,11 +41,11 @@ module.exports = { // ... modules: [ { - resolve: "@medusajs/file", + resolve: "@medusajs/medusa/file", options: { providers: [ { - resolve: "@medusajs/file-local-next", + resolve: "@medusajs/medusa/file-local-next", id: "local", options: { // provider options... diff --git a/www/apps/resources/app/architectural-modules/file/s3/page.mdx b/www/apps/resources/app/architectural-modules/file/s3/page.mdx index c0b51c6c9ff1a..95f802558c2ab 100644 --- a/www/apps/resources/app/architectural-modules/file/s3/page.mdx +++ b/www/apps/resources/app/architectural-modules/file/s3/page.mdx @@ -122,11 +122,11 @@ module.exports = { modules: [ // ... { - resolve: "@medusajs/file", + resolve: "@medusajs/medusa/file", options: { providers: [ { - resolve: "@medusajs/file-s3", + resolve: "@medusajs/medusa/file-s3", id: "s3", options: { file_url: process.env.S3_FILE_URL, @@ -354,11 +354,11 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/file", + resolve: "@medusajs/medusa/file", options: { providers: [ { - resolve: "@medusajs/file-s3", + resolve: "@medusajs/medusa/file-s3", id: "s3", options: { // ... diff --git a/www/apps/resources/app/architectural-modules/notification/local/page.mdx b/www/apps/resources/app/architectural-modules/notification/local/page.mdx index 36029c62506d1..806bda5ac1913 100644 --- a/www/apps/resources/app/architectural-modules/notification/local/page.mdx +++ b/www/apps/resources/app/architectural-modules/notification/local/page.mdx @@ -35,12 +35,12 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/notification", + resolve: "@medusajs/medusa/notification", options: { providers: [ // ... { - resolve: "@medusajs/notification-local", + resolve: "@medusajs/medusa/notification-local", id: "local", options: { channels: ["email"], diff --git a/www/apps/resources/app/architectural-modules/notification/page.mdx b/www/apps/resources/app/architectural-modules/notification/page.mdx index d3e84cce836bc..120d8b4ce5144 100644 --- a/www/apps/resources/app/architectural-modules/notification/page.mdx +++ b/www/apps/resources/app/architectural-modules/notification/page.mdx @@ -40,12 +40,12 @@ module.exports = { modules: [ // ... { - resolve: "@medusajs/notification", + resolve: "@medusajs/medusa/notification", options: { providers: [ // ... { - resolve: "@medusajs/notification-local", + resolve: "@medusajs/medusa/notification-local", id: "notification", options: { channels: ["email"], diff --git a/www/apps/resources/app/architectural-modules/notification/sendgrid/page.mdx b/www/apps/resources/app/architectural-modules/notification/sendgrid/page.mdx index e668d218be795..d01cf7532dca9 100644 --- a/www/apps/resources/app/architectural-modules/notification/sendgrid/page.mdx +++ b/www/apps/resources/app/architectural-modules/notification/sendgrid/page.mdx @@ -46,12 +46,12 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/notification", + resolve: "@medusajs/medusa/notification", options: { providers: [ // ... { - resolve: "@medusajs/notification-sendgrid", + resolve: "@medusajs/medusa/notification-sendgrid", id: "sendgrid", options: { channels: ["email"], diff --git a/www/apps/resources/app/architectural-modules/workflow-engine/in-memory/page.mdx b/www/apps/resources/app/architectural-modules/workflow-engine/in-memory/page.mdx index b1c616767c009..6ef9849d7a95c 100644 --- a/www/apps/resources/app/architectural-modules/workflow-engine/in-memory/page.mdx +++ b/www/apps/resources/app/architectural-modules/workflow-engine/in-memory/page.mdx @@ -33,7 +33,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/workflow-engine-inmemory" + resolve: "@medusajs/medusa/workflow-engine-inmemory" } ], }) diff --git a/www/apps/resources/app/architectural-modules/workflow-engine/redis/page.mdx b/www/apps/resources/app/architectural-modules/workflow-engine/redis/page.mdx index 9334e26719cbf..3790d000ba3a6 100644 --- a/www/apps/resources/app/architectural-modules/workflow-engine/redis/page.mdx +++ b/www/apps/resources/app/architectural-modules/workflow-engine/redis/page.mdx @@ -34,7 +34,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/workflow-engine-redis", + resolve: "@medusajs/medusa/workflow-engine-redis", options: { redis: { url: process.env.WE_REDIS_URL, diff --git a/www/apps/resources/app/commerce-modules/auth/module-options/page.mdx b/www/apps/resources/app/commerce-modules/auth/module-options/page.mdx index ca0e7c310b791..99c84c7cf8f62 100644 --- a/www/apps/resources/app/commerce-modules/auth/module-options/page.mdx +++ b/www/apps/resources/app/commerce-modules/auth/module-options/page.mdx @@ -35,11 +35,11 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/auth", + resolve: "@medusajs/medusa/auth", options: { providers: [ { - resolve: "@medusajs/auth-emailpass", + resolve: "@medusajs/medusa/auth-emailpass", id: "emailpass", options: { // provider options... diff --git a/www/apps/resources/app/commerce-modules/fulfillment/module-options/page.mdx b/www/apps/resources/app/commerce-modules/fulfillment/module-options/page.mdx index dcc0e53201847..b79e8b529be06 100644 --- a/www/apps/resources/app/commerce-modules/fulfillment/module-options/page.mdx +++ b/www/apps/resources/app/commerce-modules/fulfillment/module-options/page.mdx @@ -29,11 +29,11 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/fulfillment", + resolve: "@medusajs/medusa/fulfillment", options: { providers: [ { - resolve: `@medusajs/fulfillment-manual`, + resolve: `@medusajs/medusa/fulfillment-manual`, id: "manual", options: { // provider options... diff --git a/www/apps/resources/app/commerce-modules/payment/module-options/page.mdx b/www/apps/resources/app/commerce-modules/payment/module-options/page.mdx index 6cd213726da08..16c0448f14ba5 100644 --- a/www/apps/resources/app/commerce-modules/payment/module-options/page.mdx +++ b/www/apps/resources/app/commerce-modules/payment/module-options/page.mdx @@ -112,11 +112,11 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/payment", + resolve: "@medusajs/medusa/payment", options: { providers: [ { - resolve: "@medusajs/payment-stripe", + resolve: "@medusajs/medusa/payment-stripe", id: "stripe", options: { // ... diff --git a/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/page.mdx b/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/page.mdx index ac2e33e043e0e..c7ecf2ff298d1 100644 --- a/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/page.mdx +++ b/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/page.mdx @@ -40,11 +40,11 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/payment", + resolve: "@medusajs/medusa/payment", options: { providers: [ { - resolve: "@medusajs/payment-stripe", + resolve: "@medusajs/medusa/payment-stripe", id: "stripe", options: { apiKey: process.env.STRIPE_API_KEY, diff --git a/www/apps/resources/app/commerce-modules/tax/module-options/page.mdx b/www/apps/resources/app/commerce-modules/tax/module-options/page.mdx index 0f33005c53156..a820d993f1e7c 100644 --- a/www/apps/resources/app/commerce-modules/tax/module-options/page.mdx +++ b/www/apps/resources/app/commerce-modules/tax/module-options/page.mdx @@ -46,6 +46,6 @@ module.exports = defineConfig({ The objects in the array accept the following properties: -- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory. +- `resolve`: A string indicating the package name of the module provider or the path to it. - `id`: A string indicating the provider's unique name or ID. - `options`: An optional object of the module provider's options. diff --git a/www/apps/resources/app/deployment/medusa-application/railway/page.mdx b/www/apps/resources/app/deployment/medusa-application/railway/page.mdx index 5a9ad3bbfa7fe..1fe6e306339cc 100644 --- a/www/apps/resources/app/deployment/medusa-application/railway/page.mdx +++ b/www/apps/resources/app/deployment/medusa-application/railway/page.mdx @@ -121,7 +121,7 @@ So, add the following script in `package.json`: By default, your Medusa application uses modules and providers useful for development, such as the In-Memory Cache Module or the Local File Module Provider. -It’s highly recommended to instead install modules and providers suitable for production, including: +It’s highly recommended to instead use modules and providers suitable for production, including: - [Redis Cache Module](../../../architectural-modules/cache/redis/page.mdx) - [Redis Event Bus Module](../../../architectural-modules/event/redis/page.mdx) @@ -129,17 +129,6 @@ It’s highly recommended to instead install modules and providers suitable for - [S3 File Module Provider](../../../architectural-modules/file/s3/page.mdx) (or other file module providers production-ready). - [SendGrid Notification Module Provider](../../../architectural-modules/notification/sendgrid/page.mdx) (or other notification module providers production-ready). -For example, add the following dependencies in `package.json` for the Cache, Event Bus, and Workflow Engine modules: - -```json -"dependencies": { - // ... - "@medusajs/cache-redis": "rc", - "@medusajs/event-bus-redis": "rc", - "@medusajs/workflow-engine-redis": "rc" -} -``` - Then, add these modules in `medusa-config.ts`: ```ts title="medusa-config.ts" @@ -149,19 +138,19 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/cache-redis", + resolve: "@medusajs/medusa/cache-redis", options: { redisUrl: process.env.REDIS_URL, }, }, { - resolve: "@medusajs/event-bus-redis", + resolve: "@medusajs/medusa/event-bus-redis", options: { redisUrl: process.env.REDIS_URL, }, }, { - resolve: "@medusajs/workflow-engine-redis", + resolve: "@medusajs/medusa/workflow-engine-redis", options: { redis: { url: process.env.REDIS_URL, diff --git a/www/apps/resources/app/recipes/commerce-automation/page.mdx b/www/apps/resources/app/recipes/commerce-automation/page.mdx index 53151076521dc..6199b84e87f95 100644 --- a/www/apps/resources/app/recipes/commerce-automation/page.mdx +++ b/www/apps/resources/app/recipes/commerce-automation/page.mdx @@ -157,7 +157,7 @@ export const restockModelHighlights = [ // ... modules: [ { - resolve: "./modules/restock-notification", + resolve: "./src/modules/restock-notification", }, ] }) diff --git a/www/apps/resources/app/recipes/digital-products/examples/standard/page.mdx b/www/apps/resources/app/recipes/digital-products/examples/standard/page.mdx index 2f0c229e23268..ca34386c22095 100644 --- a/www/apps/resources/app/recipes/digital-products/examples/standard/page.mdx +++ b/www/apps/resources/app/recipes/digital-products/examples/standard/page.mdx @@ -196,7 +196,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "./modules/digital-product", + resolve: "./src/modules/digital-product", }, ] }) @@ -1512,15 +1512,15 @@ module.exports = defineConfig({ modules: [ // ... { - resolve: "@medusajs/fulfillment", + resolve: "@medusajs/medusa/fulfillment", options: { providers: [ { - resolve: "@medusajs/fulfillment-manual", + resolve: "@medusajs/medusa/fulfillment-manual", id: "manual", }, { - resolve: "./modules/digital-product-fulfillment", + resolve: "./src/modules/digital-product-fulfillment", id: "digital", }, ], @@ -2008,11 +2008,11 @@ module.exports = defineConfig({ modules: [ // ... { - resolve: "@medusajs/notification", + resolve: "@medusajs/medusa/notification", options: { providers: [ { - resolve: "@medusajs/notification-local", + resolve: "@medusajs/medusa/notification-local", id: "local", options: { name: "Local Notification Provider", diff --git a/www/apps/resources/app/recipes/integrate-ecommerce-stack/page.mdx b/www/apps/resources/app/recipes/integrate-ecommerce-stack/page.mdx index bb1f1c36a9708..948a315b6c0b0 100644 --- a/www/apps/resources/app/recipes/integrate-ecommerce-stack/page.mdx +++ b/www/apps/resources/app/recipes/integrate-ecommerce-stack/page.mdx @@ -123,7 +123,7 @@ export const serviceHighlights = [ // ... modules: [ { - resolve: "./modules/erp", + resolve: "./src/modules/erp", options: { apiKey: process.env.ERP_API_KEY, }, diff --git a/www/apps/resources/app/recipes/marketplace/examples/restaurant-delivery/page.mdx b/www/apps/resources/app/recipes/marketplace/examples/restaurant-delivery/page.mdx index 12c8b2de8c41b..826206a882fe8 100644 --- a/www/apps/resources/app/recipes/marketplace/examples/restaurant-delivery/page.mdx +++ b/www/apps/resources/app/recipes/marketplace/examples/restaurant-delivery/page.mdx @@ -162,7 +162,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "./modules/restaurant", + resolve: "./src/modules/restaurant", }, ] }) @@ -306,7 +306,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "./modules/delivery", + resolve: "./src/modules/delivery", }, ] }) diff --git a/www/apps/resources/app/recipes/marketplace/examples/vendors/page.mdx b/www/apps/resources/app/recipes/marketplace/examples/vendors/page.mdx index 5ca0a32cdc9c0..83ed961f99e9a 100644 --- a/www/apps/resources/app/recipes/marketplace/examples/vendors/page.mdx +++ b/www/apps/resources/app/recipes/marketplace/examples/vendors/page.mdx @@ -142,7 +142,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "./modules/marketplace", + resolve: "./src/modules/marketplace", }, ] }) diff --git a/www/apps/resources/app/recipes/subscriptions/examples/standard/page.mdx b/www/apps/resources/app/recipes/subscriptions/examples/standard/page.mdx index 7c85b22b8dfcf..16ac0f8fc2c77 100644 --- a/www/apps/resources/app/recipes/subscriptions/examples/standard/page.mdx +++ b/www/apps/resources/app/recipes/subscriptions/examples/standard/page.mdx @@ -160,7 +160,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "./modules/subscription", + resolve: "./src/modules/subscription", }, ] }) diff --git a/www/apps/resources/app/test-tools-reference/moduleIntegrationTestRunner/page.mdx b/www/apps/resources/app/test-tools-reference/moduleIntegrationTestRunner/page.mdx index 739c4d5f2dbde..58bdbd8a111a4 100644 --- a/www/apps/resources/app/test-tools-reference/moduleIntegrationTestRunner/page.mdx +++ b/www/apps/resources/app/test-tools-reference/moduleIntegrationTestRunner/page.mdx @@ -19,7 +19,7 @@ import MyCustom from "../models/my-custom" moduleIntegrationTestRunner({ moduleName: HELLO_MODULE, moduleModels: [MyCustom], - resolve: "./modules/hello", + resolve: "./src/modules/hello", testSuite: ({ service }) => { // TODO write tests }, diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs index 05414ab8a1cb0..907a424dcbe5c 100644 --- a/www/apps/resources/generated/edit-dates.mjs +++ b/www/apps/resources/generated/edit-dates.mjs @@ -3,7 +3,7 @@ export const generatedEditDates = { "app/commerce-modules/auth/auth-providers/page.mdx": "2024-10-08T07:27:21.859Z", "app/commerce-modules/auth/authentication-route/page.mdx": "2024-09-05T12:06:38.155Z", "app/commerce-modules/auth/examples/page.mdx": "2024-10-15T15:02:13.794Z", - "app/commerce-modules/auth/module-options/page.mdx": "2024-10-07T15:33:05.989Z", + "app/commerce-modules/auth/module-options/page.mdx": "2024-10-15T12:52:08.930Z", "app/commerce-modules/auth/page.mdx": "2024-10-08T07:38:27.522Z", "app/commerce-modules/cart/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00", "app/commerce-modules/cart/_events/page.mdx": "2024-07-03T19:27:13+03:00", @@ -26,7 +26,7 @@ export const generatedEditDates = { "app/commerce-modules/fulfillment/concepts/page.mdx": "2024-06-19T13:02:16+00:00", "app/commerce-modules/fulfillment/fulfillment-provider/page.mdx": "2024-10-08T14:34:16.019Z", "app/commerce-modules/fulfillment/item-fulfillment/page.mdx": "2024-10-08T14:38:15.496Z", - "app/commerce-modules/fulfillment/module-options/page.mdx": "2024-09-30T08:43:53.160Z", + "app/commerce-modules/fulfillment/module-options/page.mdx": "2024-10-15T12:51:56.118Z", "app/commerce-modules/fulfillment/shipping-option/page.mdx": "2024-10-08T14:36:02.660Z", "app/commerce-modules/fulfillment/page.mdx": "2024-10-08T15:03:27.436Z", "app/commerce-modules/inventory/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00", @@ -49,11 +49,11 @@ export const generatedEditDates = { "app/commerce-modules/payment/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00", "app/commerce-modules/payment/_events/page.mdx": "2024-07-03T19:27:13+03:00", "app/commerce-modules/payment/examples/page.mdx": "2024-10-15T14:59:55.208Z", - "app/commerce-modules/payment/module-options/page.mdx": "2024-10-09T10:49:26.079Z", + "app/commerce-modules/payment/module-options/page.mdx": "2024-10-15T12:51:40.574Z", "app/commerce-modules/payment/payment/page.mdx": "2024-10-09T10:59:08.463Z", "app/commerce-modules/payment/payment-collection/page.mdx": "2024-10-09T10:56:49.510Z", "app/commerce-modules/payment/payment-flow/page.mdx": "2024-10-09T11:18:53.332Z", - "app/commerce-modules/payment/payment-provider/stripe/page.mdx": "2024-10-09T11:09:27.471Z", + "app/commerce-modules/payment/payment-provider/stripe/page.mdx": "2024-10-15T12:51:45.236Z", "app/commerce-modules/payment/payment-provider/page.mdx": "2024-10-09T11:07:27.269Z", "app/commerce-modules/payment/payment-session/page.mdx": "2024-10-09T10:58:00.960Z", "app/commerce-modules/payment/webhook-events/page.mdx": "2024-10-09T11:11:05.413Z", @@ -120,7 +120,7 @@ export const generatedEditDates = { "app/contribution-guidelines/docs/page.mdx": "2024-05-13T18:55:11+03:00", "app/create-medusa-app/page.mdx": "2024-08-05T11:10:55+03:00", "app/deployment/admin/vercel/page.mdx": "2024-07-31T17:01:33+03:00", - "app/deployment/medusa-application/railway/page.mdx": "2024-09-30T08:43:53.172Z", + "app/deployment/medusa-application/railway/page.mdx": "2024-10-15T12:50:50.981Z", "app/deployment/storefront/vercel/page.mdx": "2024-07-26T07:21:31+00:00", "app/deployment/page.mdx": "2024-07-25T09:55:22+03:00", "app/integrations/page.mdx": "2024-07-19T08:49:08+00:00", @@ -129,19 +129,19 @@ export const generatedEditDates = { "app/medusa-workflows-reference/page.mdx": "2024-09-30T08:43:53.174Z", "app/nextjs-starter/page.mdx": "2024-07-01T10:21:19+03:00", "app/recipes/b2b/page.mdx": "2024-10-03T13:07:44.153Z", - "app/recipes/commerce-automation/page.mdx": "2024-10-03T13:07:44.147Z", - "app/recipes/digital-products/examples/standard/page.mdx": "2024-10-04T09:44:55.176Z", + "app/recipes/commerce-automation/page.mdx": "2024-10-16T08:52:01.585Z", + "app/recipes/digital-products/examples/standard/page.mdx": "2024-10-16T08:52:12.991Z", "app/recipes/digital-products/page.mdx": "2024-10-03T13:07:44.147Z", "app/recipes/ecommerce/page.mdx": "2024-06-09T15:18:43+02:00", - "app/recipes/integrate-ecommerce-stack/page.mdx": "2024-10-03T13:07:44.146Z", - "app/recipes/marketplace/examples/vendors/page.mdx": "2024-10-03T13:07:44.153Z", + "app/recipes/integrate-ecommerce-stack/page.mdx": "2024-10-16T08:52:16.760Z", + "app/recipes/marketplace/examples/vendors/page.mdx": "2024-10-16T08:52:24.906Z", "app/recipes/marketplace/page.mdx": "2024-10-03T13:07:44.153Z", "app/recipes/multi-region-store/page.mdx": "2024-10-03T13:07:13.813Z", "app/recipes/omnichannel/page.mdx": "2024-10-03T13:07:14.384Z", "app/recipes/oms/page.mdx": "2024-07-01T10:21:19+03:00", "app/recipes/personalized-products/page.mdx": "2024-10-03T13:07:44.153Z", "app/recipes/pos/page.mdx": "2024-10-03T13:07:13.964Z", - "app/recipes/subscriptions/examples/standard/page.mdx": "2024-10-04T11:13:47.459Z", + "app/recipes/subscriptions/examples/standard/page.mdx": "2024-10-16T08:52:27.606Z", "app/recipes/subscriptions/page.mdx": "2024-10-03T13:07:44.155Z", "app/recipes/page.mdx": "2024-07-11T15:56:41+00:00", "app/service-factory-reference/methods/create/page.mdx": "2024-07-31T17:01:33+03:00", @@ -217,30 +217,30 @@ export const generatedEditDates = { "app/commerce-modules/api-key/page.mdx": "2024-10-07T13:57:33.042Z", "app/commerce-modules/auth/create-actor-type/page.mdx": "2024-10-08T07:31:11.256Z", "app/architectural-modules/page.mdx": "2024-05-28T13:25:03+03:00", - "app/architectural-modules/workflow-engine/redis/page.mdx": "2024-09-30T08:43:53.152Z", + "app/architectural-modules/workflow-engine/redis/page.mdx": "2024-10-15T12:50:59.507Z", "app/commerce-modules/api-key/examples/page.mdx": "2024-10-15T14:58:58.994Z", - "app/architectural-modules/notification/sendgrid/page.mdx": "2024-09-30T08:43:53.151Z", + "app/architectural-modules/notification/sendgrid/page.mdx": "2024-10-15T12:51:25.569Z", "app/commerce-modules/api-key/concepts/page.mdx": "2024-10-07T13:59:37.529Z", "app/architectural-modules/workflow-engine/page.mdx": "2024-05-28T13:25:03+03:00", "app/_events-reference/page.mdx": "2024-07-03T19:27:13+03:00", "app/architectural-modules/cache/page.mdx": "2024-05-28T13:25:03+03:00", - "app/architectural-modules/file/s3/page.mdx": "2024-09-30T08:43:53.150Z", + "app/architectural-modules/file/s3/page.mdx": "2024-10-15T12:51:14.315Z", "app/commerce-modules/api-key/_events/page.mdx": "2024-07-03T19:27:13+03:00", "app/commerce-modules/api-key/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00", - "app/architectural-modules/cache/redis/page.mdx": "2024-09-30T08:43:53.149Z", - "app/architectural-modules/event/redis/page.mdx": "2024-09-30T08:43:53.149Z", - "app/architectural-modules/event/local/page.mdx": "2024-09-30T08:43:53.149Z", - "app/architectural-modules/workflow-engine/in-memory/page.mdx": "2024-09-30T08:43:53.151Z", - "app/architectural-modules/cache/in-memory/page.mdx": "2024-09-30T08:43:53.148Z", - "app/architectural-modules/notification/local/page.mdx": "2024-09-30T08:43:53.150Z", - "app/architectural-modules/file/local/page.mdx": "2024-09-30T08:43:53.150Z", + "app/architectural-modules/cache/redis/page.mdx": "2024-10-15T12:50:01.730Z", + "app/architectural-modules/event/redis/page.mdx": "2024-10-15T12:50:40.506Z", + "app/architectural-modules/event/local/page.mdx": "2024-10-15T12:50:37.512Z", + "app/architectural-modules/workflow-engine/in-memory/page.mdx": "2024-10-15T12:50:57.249Z", + "app/architectural-modules/cache/in-memory/page.mdx": "2024-10-15T12:49:57.608Z", + "app/architectural-modules/notification/local/page.mdx": "2024-10-15T12:51:21.284Z", + "app/architectural-modules/file/local/page.mdx": "2024-10-15T12:51:07.033Z", "app/architectural-modules/notification/send-notification/page.mdx": "2024-09-30T08:43:53.151Z", "app/architectural-modules/file/page.mdx": "2024-07-01T10:21:19+03:00", "app/architectural-modules/event/page.mdx": "2024-05-28T13:25:03+03:00", - "app/architectural-modules/cache/create/page.mdx": "2024-09-30T08:43:53.148Z", + "app/architectural-modules/cache/create/page.mdx": "2024-10-16T08:51:35.074Z", "app/admin-widget-injection-zones/page.mdx": "2024-09-30T08:43:53.147Z", - "app/architectural-modules/notification/page.mdx": "2024-09-30T08:43:53.151Z", - "app/architectural-modules/event/create/page.mdx": "2024-09-30T08:43:53.149Z", + "app/architectural-modules/notification/page.mdx": "2024-10-15T12:51:28.735Z", + "app/architectural-modules/event/create/page.mdx": "2024-10-16T08:51:41.334Z", "app/troubleshooting/deployment/page.mdx": "2024-08-19T07:19:40.924Z", "references/core_flows/Order/functions/core_flows.Order.orderEditUpdateItemQuantityValidationStep/page.mdx": "2024-08-20T00:10:58.913Z", "references/core_flows/Order/functions/core_flows.Order.orderEditUpdateItemQuantityWorkflow/page.mdx": "2024-08-20T00:10:58.949Z", @@ -630,7 +630,7 @@ export const generatedEditDates = { "app/medusa-cli/commands/start/page.mdx": "2024-08-28T10:44:19.952Z", "app/medusa-cli/commands/telemtry/page.mdx": "2024-08-28T11:25:08.553Z", "app/medusa-cli/commands/user/page.mdx": "2024-08-28T10:44:52.489Z", - "app/recipes/marketplace/examples/restaurant-delivery/page.mdx": "2024-10-04T10:28:51.698Z", + "app/recipes/marketplace/examples/restaurant-delivery/page.mdx": "2024-10-16T08:52:21.948Z", "references/types/HttpTypes/interfaces/types.HttpTypes.AdminCreateCustomerGroup/page.mdx": "2024-08-30T00:11:02.074Z", "references/types/HttpTypes/interfaces/types.HttpTypes.AdminCreateReservation/page.mdx": "2024-08-30T00:11:02.342Z", "references/types/HttpTypes/interfaces/types.HttpTypes.AdminCustomerGroup/page.mdx": "2024-10-03T00:11:52.871Z", @@ -757,7 +757,7 @@ export const generatedEditDates = { "references/types/HttpTypes/interfaces/types.HttpTypes.AdminWorkflowExecutionResponse/page.mdx": "2024-08-30T00:11:02.514Z", "references/types/interfaces/types.BaseReturnItem/page.mdx": "2024-08-30T00:11:02.538Z", "app/test-tools-reference/medusaIntegrationTestRunner/page.mdx": "2024-09-02T12:12:48.492Z", - "app/test-tools-reference/moduleIntegrationTestRunner/page.mdx": "2024-09-02T12:18:33.134Z", + "app/test-tools-reference/moduleIntegrationTestRunner/page.mdx": "2024-10-16T08:52:30.701Z", "app/test-tools-reference/page.mdx": "2024-09-02T12:25:44.922Z", "references/types/HttpTypes/interfaces/types.HttpTypes.AdminGetInvitesParams/page.mdx": "2024-09-03T00:10:55.319Z", "references/types/HttpTypes/interfaces/types.HttpTypes.AdminInventoryLevel/page.mdx": "2024-09-17T00:10:58.487Z", diff --git a/www/apps/resources/references/auth_provider/classes/auth_provider.AbstractAuthModuleProvider/page.mdx b/www/apps/resources/references/auth_provider/classes/auth_provider.AbstractAuthModuleProvider/page.mdx index 3e63b8a98c86a..52d5cfabd1f6c 100644 --- a/www/apps/resources/references/auth_provider/classes/auth_provider.AbstractAuthModuleProvider/page.mdx +++ b/www/apps/resources/references/auth_provider/classes/auth_provider.AbstractAuthModuleProvider/page.mdx @@ -436,7 +436,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/framework/auth", + resolve: "@medusajs/medusa/auth", options: { providers: [ { diff --git a/www/apps/resources/references/file/classes/file.AbstractFileProviderService/page.mdx b/www/apps/resources/references/file/classes/file.AbstractFileProviderService/page.mdx index a54351993f90f..bfa9ecd1f6ae5 100644 --- a/www/apps/resources/references/file/classes/file.AbstractFileProviderService/page.mdx +++ b/www/apps/resources/references/file/classes/file.AbstractFileProviderService/page.mdx @@ -214,7 +214,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/framework/file", + resolve: "@medusajs/medusa/file", options: { providers: [ { diff --git a/www/apps/resources/references/fulfillment_provider/classes/fulfillment_provider.AbstractFulfillmentProviderService/page.mdx b/www/apps/resources/references/fulfillment_provider/classes/fulfillment_provider.AbstractFulfillmentProviderService/page.mdx index 1a7463f5625c8..3030c38ba28fd 100644 --- a/www/apps/resources/references/fulfillment_provider/classes/fulfillment_provider.AbstractFulfillmentProviderService/page.mdx +++ b/www/apps/resources/references/fulfillment_provider/classes/fulfillment_provider.AbstractFulfillmentProviderService/page.mdx @@ -477,7 +477,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/framework/fulfillment", + resolve: "@medusajs/medusa/fulfillment", options: { providers: [ { diff --git a/www/apps/resources/references/notification/classes/notification.AbstractNotificationProviderService/page.mdx b/www/apps/resources/references/notification/classes/notification.AbstractNotificationProviderService/page.mdx index e45fc15e76d55..152b2a4620317 100644 --- a/www/apps/resources/references/notification/classes/notification.AbstractNotificationProviderService/page.mdx +++ b/www/apps/resources/references/notification/classes/notification.AbstractNotificationProviderService/page.mdx @@ -167,7 +167,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/framework/notification", + resolve: "@medusajs/medusa/notification", options: { providers: [ { diff --git a/www/apps/resources/references/payment_provider/classes/payment_provider.AbstractPaymentProvider/page.mdx b/www/apps/resources/references/payment_provider/classes/payment_provider.AbstractPaymentProvider/page.mdx index a858e15d26f46..039bedc4307df 100644 --- a/www/apps/resources/references/payment_provider/classes/payment_provider.AbstractPaymentProvider/page.mdx +++ b/www/apps/resources/references/payment_provider/classes/payment_provider.AbstractPaymentProvider/page.mdx @@ -704,7 +704,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/framework/payment", + resolve: "@medusajs/medusa/payment", options: { providers: [ { diff --git a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/auth-provider.ts b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/auth-provider.ts index 949a2171fdf44..dab3231b3bfc4 100644 --- a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/auth-provider.ts +++ b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/auth-provider.ts @@ -59,11 +59,11 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/framework/auth", + resolve: "@medusajs/medusa/auth", options: { providers: [ { - resolve: "./modules/my-auth", + resolve: "./src/modules/my-auth", id: "my-auth", options: { // provider options... diff --git a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/file.ts b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/file.ts index 001bb6485e9a9..7b00109fe97aa 100644 --- a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/file.ts +++ b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/file.ts @@ -69,7 +69,7 @@ module.exports = defineConfig({ options: { providers: [ { - resolve: "./modules/my-file", + resolve: "./src/modules/my-file", id: "my-file", options: { // provider options... diff --git a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/fulfillment-provider.ts b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/fulfillment-provider.ts index f5639d112cfe4..434ed68f7c80f 100644 --- a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/fulfillment-provider.ts +++ b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/fulfillment-provider.ts @@ -59,11 +59,11 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/framework/fulfillment", + resolve: "@medusajs/medusa/fulfillment", options: { providers: [ { - resolve: "./modules/my-fulfillment", + resolve: "./src/modules/my-fulfillment", id: "my-fulfillment", options: { // provider options... diff --git a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/notification.ts b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/notification.ts index 96736761bc887..f83fe0dd2d1db 100644 --- a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/notification.ts +++ b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/notification.ts @@ -69,11 +69,11 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/framework/notification", + resolve: "@medusajs/medusa/notification", options: { providers: [ { - resolve: "./modules/my-notification", + resolve: "./src/modules/my-notification", id: "my-notification", options: { channels: ["email"], diff --git a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/payment-provider.ts b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/payment-provider.ts index fff1511015a0d..842ea16cd3223 100644 --- a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/payment-provider.ts +++ b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/payment-provider.ts @@ -68,11 +68,11 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/framework/payment", + resolve: "@medusajs/medusa/payment", options: { providers: [ { - resolve: "./modules/my-payment", + resolve: "./src/modules/my-payment", id: "my-payment", options: { // provider options... diff --git a/yarn.lock b/yarn.lock index 7be54ccba1837..e91411582ae61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3345,6 +3345,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/aix-ppc64@npm:0.21.5" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/aix-ppc64@npm:0.23.1" @@ -3373,6 +3380,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm64@npm:0.21.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/android-arm64@npm:0.23.1" @@ -3401,6 +3415,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm@npm:0.21.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/android-arm@npm:0.23.1" @@ -3429,6 +3450,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-x64@npm:0.21.5" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/android-x64@npm:0.23.1" @@ -3457,6 +3485,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-arm64@npm:0.21.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/darwin-arm64@npm:0.23.1" @@ -3485,6 +3520,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-x64@npm:0.21.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/darwin-x64@npm:0.23.1" @@ -3513,6 +3555,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-arm64@npm:0.21.5" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/freebsd-arm64@npm:0.23.1" @@ -3541,6 +3590,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-x64@npm:0.21.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/freebsd-x64@npm:0.23.1" @@ -3569,6 +3625,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm64@npm:0.21.5" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/linux-arm64@npm:0.23.1" @@ -3597,6 +3660,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm@npm:0.21.5" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/linux-arm@npm:0.23.1" @@ -3625,6 +3695,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ia32@npm:0.21.5" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/linux-ia32@npm:0.23.1" @@ -3653,6 +3730,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-loong64@npm:0.21.5" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/linux-loong64@npm:0.23.1" @@ -3681,6 +3765,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-mips64el@npm:0.21.5" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/linux-mips64el@npm:0.23.1" @@ -3709,6 +3800,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ppc64@npm:0.21.5" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/linux-ppc64@npm:0.23.1" @@ -3737,6 +3835,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-riscv64@npm:0.21.5" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/linux-riscv64@npm:0.23.1" @@ -3765,6 +3870,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-s390x@npm:0.21.5" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/linux-s390x@npm:0.23.1" @@ -3793,6 +3905,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-x64@npm:0.21.5" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/linux-x64@npm:0.23.1" @@ -3821,6 +3940,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/netbsd-x64@npm:0.21.5" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/netbsd-x64@npm:0.23.1" @@ -3856,6 +3982,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/openbsd-x64@npm:0.21.5" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/openbsd-x64@npm:0.23.1" @@ -3884,6 +4017,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/sunos-x64@npm:0.21.5" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/sunos-x64@npm:0.23.1" @@ -3912,6 +4052,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-arm64@npm:0.21.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/win32-arm64@npm:0.23.1" @@ -3940,6 +4087,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-ia32@npm:0.21.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/win32-ia32@npm:0.23.1" @@ -3968,6 +4122,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-x64@npm:0.21.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/win32-x64@npm:0.23.1" @@ -4984,6 +5145,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:0.3.9": version: 0.3.9 resolution: "@jridgewell/trace-mapping@npm:0.3.9" @@ -5136,6 +5304,7 @@ __metadata: tsup: 8.0.1 typescript: 5.3.3 vite: ^5.2.11 + vitest: ^2.1.3 peerDependencies: vite: ^5.0.0 languageName: unknown @@ -5738,6 +5907,22 @@ __metadata: languageName: unknown linkType: soft +"@medusajs/locking-redis@^0.0.1, @medusajs/locking-redis@workspace:packages/modules/providers/locking-redis": + version: 0.0.0-use.local + resolution: "@medusajs/locking-redis@workspace:packages/modules/providers/locking-redis" + dependencies: + "@medusajs/framework": ^0.0.1 + "@swc/core": ^1.7.28 + "@swc/jest": ^0.2.36 + ioredis: ^5.4.1 + jest: ^29.7.0 + rimraf: ^5.0.1 + typescript: ^5.6.2 + peerDependencies: + "@medusajs/framework": ^0.0.1 + languageName: unknown + linkType: soft + "@medusajs/locking@^0.0.1, @medusajs/locking@workspace:packages/modules/locking": version: 0.0.0-use.local resolution: "@medusajs/locking@workspace:packages/modules/locking" @@ -5873,6 +6058,7 @@ __metadata: "@medusajs/inventory-next": ^0.0.3 "@medusajs/link-modules": ^0.2.11 "@medusajs/locking": ^0.0.1 + "@medusajs/locking-redis": ^0.0.1 "@medusajs/notification": ^0.1.2 "@medusajs/notification-local": ^0.0.1 "@medusajs/notification-sendgrid": ^0.0.1 @@ -10775,6 +10961,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm-eabi@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.24.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@rollup/rollup-android-arm64@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-android-arm64@npm:4.17.2" @@ -10782,6 +10975,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm64@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-android-arm64@npm:4.24.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-arm64@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-darwin-arm64@npm:4.17.2" @@ -10789,6 +10989,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-arm64@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.24.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-x64@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-darwin-x64@npm:4.17.2" @@ -10796,6 +11003,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-x64@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.24.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-gnueabihf@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.17.2" @@ -10803,6 +11017,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-gnueabihf@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.24.0" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-musleabihf@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.17.2" @@ -10810,6 +11031,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-musleabihf@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.24.0" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-gnu@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.17.2" @@ -10817,6 +11045,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.24.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-musl@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-linux-arm64-musl@npm:4.17.2" @@ -10824,6 +11059,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-musl@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.24.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-powerpc64le-gnu@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.17.2" @@ -10831,6 +11073,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.24.0" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-riscv64-gnu@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.17.2" @@ -10838,6 +11087,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-riscv64-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.24.0" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-s390x-gnu@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.17.2" @@ -10845,6 +11101,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-s390x-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.24.0" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-gnu@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-linux-x64-gnu@npm:4.17.2" @@ -10852,6 +11115,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.24.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-musl@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-linux-x64-musl@npm:4.17.2" @@ -10859,6 +11129,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-musl@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.24.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-win32-arm64-msvc@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.17.2" @@ -10866,6 +11143,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-arm64-msvc@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.24.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-win32-ia32-msvc@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.17.2" @@ -10873,6 +11157,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-ia32-msvc@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.24.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@rollup/rollup-win32-x64-msvc@npm:4.17.2": version: 4.17.2 resolution: "@rollup/rollup-win32-x64-msvc@npm:4.17.2" @@ -10880,6 +11171,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-x64-msvc@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.24.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rushstack/node-core-library@npm:4.2.0": version: 4.2.0 resolution: "@rushstack/node-core-library@npm:4.2.0" @@ -13068,6 +13366,13 @@ __metadata: languageName: node linkType: hard +"@types/estree@npm:1.0.6": + version: 1.0.6 + resolution: "@types/estree@npm:1.0.6" + checksum: cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a + languageName: node + linkType: hard + "@types/estree@npm:^0.0.51": version: 0.0.51 resolution: "@types/estree@npm:0.0.51" @@ -14050,6 +14355,38 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:2.1.3": + version: 2.1.3 + resolution: "@vitest/expect@npm:2.1.3" + dependencies: + "@vitest/spy": 2.1.3 + "@vitest/utils": 2.1.3 + chai: ^5.1.1 + tinyrainbow: ^1.2.0 + checksum: 0837adcbb938feebcc083664afc5c4d12e42f1f2442b6f1bedc6b5650a8ff2448b1f10713b45afb099c839fb5cf766c971736267fa9b0fe2ac87f3e2d7f782c2 + languageName: node + linkType: hard + +"@vitest/mocker@npm:2.1.3": + version: 2.1.3 + resolution: "@vitest/mocker@npm:2.1.3" + dependencies: + "@vitest/spy": 2.1.3 + estree-walker: ^3.0.3 + magic-string: ^0.30.11 + peerDependencies: + "@vitest/spy": 2.1.3 + msw: ^2.3.5 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 03c80628d092244f21a0ba9041665fc75f987d0d11fab1ae0b7027ec21e503f65057e8c24b936602c5f852d83fbb183da13d05dba117c99785b41b3dafd105ce + languageName: node + linkType: hard + "@vitest/pretty-format@npm:2.0.5": version: 2.0.5 resolution: "@vitest/pretty-format@npm:2.0.5" @@ -14068,6 +14405,15 @@ __metadata: languageName: node linkType: hard +"@vitest/pretty-format@npm:2.1.3, @vitest/pretty-format@npm:^2.1.3": + version: 2.1.3 + resolution: "@vitest/pretty-format@npm:2.1.3" + dependencies: + tinyrainbow: ^1.2.0 + checksum: 5a6ee872a8adf5e2764f2b5b2276d8a2199be4ef14777ab693428caf359481851400af10b59721d4972289c955ffe7277954a662b04cfb10233824574c7074ba + languageName: node + linkType: hard + "@vitest/runner@npm:0.32.4": version: 0.32.4 resolution: "@vitest/runner@npm:0.32.4" @@ -14079,6 +14425,16 @@ __metadata: languageName: node linkType: hard +"@vitest/runner@npm:2.1.3": + version: 2.1.3 + resolution: "@vitest/runner@npm:2.1.3" + dependencies: + "@vitest/utils": 2.1.3 + pathe: ^1.1.2 + checksum: d5b077643265d10025e22fa64a0e54c3d4fddc23e05f9fcd143dbcc4080851b0df31985986e57890a974577a18d3af624758b6062801d7dd96f9b4f2eaf591f1 + languageName: node + linkType: hard + "@vitest/snapshot@npm:0.32.4": version: 0.32.4 resolution: "@vitest/snapshot@npm:0.32.4" @@ -14090,6 +14446,17 @@ __metadata: languageName: node linkType: hard +"@vitest/snapshot@npm:2.1.3": + version: 2.1.3 + resolution: "@vitest/snapshot@npm:2.1.3" + dependencies: + "@vitest/pretty-format": 2.1.3 + magic-string: ^0.30.11 + pathe: ^1.1.2 + checksum: a3dcea6a5f7581b6a34dc3bf5f7bd42a05e2ccf6e1171d9f1b759688aebe650e6412564d066aeaa45e83ac549d453b6a3edcf774a8ac728c0c639f8dc919039f + languageName: node + linkType: hard + "@vitest/spy@npm:0.32.4": version: 0.32.4 resolution: "@vitest/spy@npm:0.32.4" @@ -14108,6 +14475,15 @@ __metadata: languageName: node linkType: hard +"@vitest/spy@npm:2.1.3": + version: 2.1.3 + resolution: "@vitest/spy@npm:2.1.3" + dependencies: + tinyspy: ^3.0.0 + checksum: 8d85a5c2848c5bd81892af989aebad65d0c7ae74094aa98ad4f35ecf80755259c7a748a8e7bf683b2906fac29a51fc0ffa82f8fc073b36dbd8a0418261fccdba + languageName: node + linkType: hard + "@vitest/utils@npm:0.32.4": version: 0.32.4 resolution: "@vitest/utils@npm:0.32.4" @@ -14131,6 +14507,17 @@ __metadata: languageName: node linkType: hard +"@vitest/utils@npm:2.1.3": + version: 2.1.3 + resolution: "@vitest/utils@npm:2.1.3" + dependencies: + "@vitest/pretty-format": 2.1.3 + loupe: ^3.1.1 + tinyrainbow: ^1.2.0 + checksum: 55a044e43b84c0f8f573d8578107f26440678b6f506c8d9fee88b7ef120d19efd27c9be77985c107113b0f3f3db298dcee57074e1c1c214bee7a097fd08a209b + languageName: node + linkType: hard + "@vitest/utils@npm:^2.0.5": version: 2.1.2 resolution: "@vitest/utils@npm:2.1.2" @@ -18765,6 +19152,86 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.21.3": + version: 0.21.5 + resolution: "esbuild@npm:0.21.5" + dependencies: + "@esbuild/aix-ppc64": 0.21.5 + "@esbuild/android-arm": 0.21.5 + "@esbuild/android-arm64": 0.21.5 + "@esbuild/android-x64": 0.21.5 + "@esbuild/darwin-arm64": 0.21.5 + "@esbuild/darwin-x64": 0.21.5 + "@esbuild/freebsd-arm64": 0.21.5 + "@esbuild/freebsd-x64": 0.21.5 + "@esbuild/linux-arm": 0.21.5 + "@esbuild/linux-arm64": 0.21.5 + "@esbuild/linux-ia32": 0.21.5 + "@esbuild/linux-loong64": 0.21.5 + "@esbuild/linux-mips64el": 0.21.5 + "@esbuild/linux-ppc64": 0.21.5 + "@esbuild/linux-riscv64": 0.21.5 + "@esbuild/linux-s390x": 0.21.5 + "@esbuild/linux-x64": 0.21.5 + "@esbuild/netbsd-x64": 0.21.5 + "@esbuild/openbsd-x64": 0.21.5 + "@esbuild/sunos-x64": 0.21.5 + "@esbuild/win32-arm64": 0.21.5 + "@esbuild/win32-ia32": 0.21.5 + "@esbuild/win32-x64": 0.21.5 + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: fa08508adf683c3f399e8a014a6382a6b65542213431e26206c0720e536b31c09b50798747c2a105a4bbba1d9767b8d3615a74c2f7bf1ddf6d836cd11eb672de + languageName: node + linkType: hard + "escalade@npm:^3.1.1, escalade@npm:^3.1.2": version: 3.1.2 resolution: "escalade@npm:3.1.2" @@ -23898,6 +24365,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.11": + version: 0.30.12 + resolution: "magic-string@npm:0.30.12" + dependencies: + "@jridgewell/sourcemap-codec": ^1.5.0 + checksum: 469f457d18af37dfcca8617086ea8a65bcd8b60ba8a1182cb024ce43e470ace3c9d1cb6bee58d3b311768fb16bc27bd50bdeebcaa63dadd0fd46cac4d2e11d5f + languageName: node + linkType: hard + "make-dir@npm:^3.0.0, make-dir@npm:^3.0.2": version: 3.1.0 resolution: "make-dir@npm:3.1.0" @@ -26779,6 +27255,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.4.43": + version: 8.4.47 + resolution: "postcss@npm:8.4.47" + dependencies: + nanoid: ^3.3.7 + picocolors: ^1.1.0 + source-map-js: ^1.2.1 + checksum: 929f68b5081b7202709456532cee2a145c1843d391508c5a09de2517e8c4791638f71dd63b1898dba6712f8839d7a6da046c72a5e44c162e908f5911f57b5f44 + languageName: node + linkType: hard + "postgres-array@npm:~2.0.0": version: 2.0.0 resolution: "postgres-array@npm:2.0.0" @@ -28735,6 +29222,69 @@ __metadata: languageName: node linkType: hard +"rollup@npm:^4.20.0": + version: 4.24.0 + resolution: "rollup@npm:4.24.0" + dependencies: + "@rollup/rollup-android-arm-eabi": 4.24.0 + "@rollup/rollup-android-arm64": 4.24.0 + "@rollup/rollup-darwin-arm64": 4.24.0 + "@rollup/rollup-darwin-x64": 4.24.0 + "@rollup/rollup-linux-arm-gnueabihf": 4.24.0 + "@rollup/rollup-linux-arm-musleabihf": 4.24.0 + "@rollup/rollup-linux-arm64-gnu": 4.24.0 + "@rollup/rollup-linux-arm64-musl": 4.24.0 + "@rollup/rollup-linux-powerpc64le-gnu": 4.24.0 + "@rollup/rollup-linux-riscv64-gnu": 4.24.0 + "@rollup/rollup-linux-s390x-gnu": 4.24.0 + "@rollup/rollup-linux-x64-gnu": 4.24.0 + "@rollup/rollup-linux-x64-musl": 4.24.0 + "@rollup/rollup-win32-arm64-msvc": 4.24.0 + "@rollup/rollup-win32-ia32-msvc": 4.24.0 + "@rollup/rollup-win32-x64-msvc": 4.24.0 + "@types/estree": 1.0.6 + fsevents: ~2.3.2 + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-powerpc64le-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 77fb549c1de8afd1142d2da765adbb0cdab9f13c47df5217f00b5cf40b74219caa48c6ba2157f6249313ee81b6fa4c4fa8b3d2a0347ad6220739e00e580a808d + languageName: node + linkType: hard + "root@workspace:.": version: 0.0.0-use.local resolution: "root@workspace:." @@ -29548,6 +30098,13 @@ __metadata: languageName: node linkType: hard +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + "source-map-support@npm:0.5.13": version: 0.5.13 resolution: "source-map-support@npm:0.5.13" @@ -29805,7 +30362,7 @@ __metadata: languageName: node linkType: hard -"std-env@npm:^3.3.3": +"std-env@npm:^3.3.3, std-env@npm:^3.7.0": version: 3.7.0 resolution: "std-env@npm:3.7.0" checksum: 60edf2d130a4feb7002974af3d5a5f3343558d1ccf8d9b9934d225c638606884db4a20d2fe6440a09605bca282af6b042ae8070a10490c0800d69e82e478f41e @@ -30603,6 +31160,20 @@ __metadata: languageName: node linkType: hard +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + +"tinyexec@npm:^0.3.0": + version: 0.3.0 + resolution: "tinyexec@npm:0.3.0" + checksum: 138a4f4241aea6b6312559508468ab275a31955e66e2f57ed206e0aaabecee622624f208c5740345f0a66e33478fd065e359ed1eb1269eb6fd4fa25d44d0ba3b + languageName: node + linkType: hard + "tinypool@npm:^0.5.0": version: 0.5.0 resolution: "tinypool@npm:0.5.0" @@ -30610,6 +31181,13 @@ __metadata: languageName: node linkType: hard +"tinypool@npm:^1.0.0": + version: 1.0.1 + resolution: "tinypool@npm:1.0.1" + checksum: 90939d6a03f1519c61007bf416632dc1f0b9c1a9dd673c179ccd9e36a408437384f984fc86555a5d040d45b595abc299c3bb39d354439e98a090766b5952e73d + languageName: node + linkType: hard + "tinyrainbow@npm:^1.2.0": version: 1.2.0 resolution: "tinyrainbow@npm:1.2.0" @@ -32157,6 +32735,20 @@ __metadata: languageName: node linkType: hard +"vite-node@npm:2.1.3": + version: 2.1.3 + resolution: "vite-node@npm:2.1.3" + dependencies: + cac: ^6.7.14 + debug: ^4.3.6 + pathe: ^1.1.2 + vite: ^5.0.0 + bin: + vite-node: vite-node.mjs + checksum: 1b06139880a8170651e025e8c35aa92a917f8ec8f24507cda5bf4be09843f6447e1f494932a8d7eb98124f1c8c9fee02283ef318ddd57e2b861d2d85a409a206 + languageName: node + linkType: hard + "vite-plugin-inspect@npm:^0.8.7": version: 0.8.7 resolution: "vite-plugin-inspect@npm:0.8.7" @@ -32226,6 +32818,49 @@ __metadata: languageName: node linkType: hard +"vite@npm:^5.0.0": + version: 5.4.9 + resolution: "vite@npm:5.4.9" + dependencies: + esbuild: ^0.21.3 + fsevents: ~2.3.3 + postcss: ^8.4.43 + rollup: ^4.20.0 + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: e9c59f2c639047e37c79bbbb151c7a55a3dc27932957cf4cf0447ee0bdcc1ddfd9b1fb3ba0465371c01ba3616d62561327855794c2d652213c3a10a32e6d369d + languageName: node + linkType: hard + "vite@npm:^5.2.11": version: 5.2.11 resolution: "vite@npm:5.2.11" @@ -32326,6 +32961,55 @@ __metadata: languageName: node linkType: hard +"vitest@npm:^2.1.3": + version: 2.1.3 + resolution: "vitest@npm:2.1.3" + dependencies: + "@vitest/expect": 2.1.3 + "@vitest/mocker": 2.1.3 + "@vitest/pretty-format": ^2.1.3 + "@vitest/runner": 2.1.3 + "@vitest/snapshot": 2.1.3 + "@vitest/spy": 2.1.3 + "@vitest/utils": 2.1.3 + chai: ^5.1.1 + debug: ^4.3.6 + magic-string: ^0.30.11 + pathe: ^1.1.2 + std-env: ^3.7.0 + tinybench: ^2.9.0 + tinyexec: ^0.3.0 + tinypool: ^1.0.0 + tinyrainbow: ^1.2.0 + vite: ^5.0.0 + vite-node: 2.1.3 + why-is-node-running: ^2.3.0 + peerDependencies: + "@edge-runtime/vm": "*" + "@types/node": ^18.0.0 || >=20.0.0 + "@vitest/browser": 2.1.3 + "@vitest/ui": 2.1.3 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 7688fdce37205e7f3b448039df216e103e3a52994af0201993e22decbb558d129a734001b991f3c3d80bf4a4ef91ca6a5665a7395d5b051249da60a0016eda36 + languageName: node + linkType: hard + "void-elements@npm:3.1.0": version: 3.1.0 resolution: "void-elements@npm:3.1.0" @@ -32620,6 +33304,18 @@ __metadata: languageName: node linkType: hard +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: ^2.0.0 + stackback: 0.0.2 + bin: + why-is-node-running: cli.js + checksum: 1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard + "widest-line@npm:^3.1.0": version: 3.1.0 resolution: "widest-line@npm:3.1.0"