@medusajs/store 模块版本演进全解析:从 Medusa 2.0 重构到 DML 模型与 currency 规范化的完整技术史
medusajs/store 模块版本演进全解析从 Medusa 2.0 重构到 DML 模型与 currency 规范化的完整技术史【免费下载链接】medusaThe worlds most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusamedusajs/store是 Medusa 2.x 中以模块化方式独立发布的 Store店铺/商店领域模块负责管理店铺的基础信息、默认销售渠道/区域/库存地点、支持的货币与语言环境。本篇文章以 packages/modules/store/CHANGELOG.md 为骨架结合模块源码、模型定义、服务实现、数据库迁移与集成测试完整还原该模块从 0.1.1 到 2.20.1 的演进脉络。读完你将掌握 store 模块的领域模型结构、核心服务能力、关键变更的底层动因以及如何安全地在自己的 Medusa 项目中随框架版本升级。模块定位Store 在 Medusa 2.x 中的角色在 Medusa 2.x 中系统被拆分为一组可独立发布、独立升级的业务模块moduleStore 模块是其中之一。它通过 src/index.ts 中的Module(Modules.STORE, { service: StoreModuleService })完成注册向外暴露三个可链接linkable的实体store、storeCurrency、storeLocale——这一事实由 store-module-service.spec.ts 中的集成测试明确验证。Store 模块的核心职责包括维护店铺基本信息名称、元数据 metadata记录默认销售渠道default_sales_channel_id、默认区域default_region_id、默认库存地点default_location_id管理店铺支持的货币列表及其默认货币管理店铺支持的语言环境locale列表2.12.3 起。模块的package.json声明其运行环境为 Node.js 20构建产物为dist/index.jsmain/types指向编译后的 JS 与类型声明。版本演进全景从 0.1.1 到 2.20.1CHANGELOG 记录了 store 模块的全部发布历史。其版本节奏与 Medusa 框架整体版本保持严格同步绝大多数条目为Updated dependencies依赖更新指向medusajs/framework。这说明store 模块与框架是强绑定关系模块自身功能相对稳定其迭代主要跟随框架的能力演进。Store 版本类型关键变更0.1.1Patch为所有模块统一版本号以便早期联调测试PR #67002.0.0MajorMedusa 2.0 大规模重构框架依赖升至 medusajs/framework2.0.02.1.1Patch将 store 模块迁移到 DMLData Model Language声明模型PR #104672.4.0Minor升级到 MikroORM 6PR #102922.5.0Patch让AbstractModuleService的 create 方法类型安全PR #112162.6.1Patch移除 Medusa 各包间的版本范围ranges依赖PR #117382.8.7Patch修复订单约束与收到退货相关逻辑PR #128892.11.0Patch将 peer 依赖收敛为单一包并从 framework 重新导出PR #13439同时新增默认退款原因2.12.3Patch从StoreLocale中移除default_localePR #14300修复迁移生成器生成的 importPR #143152.13.0Minor例行 minor bump同步框架 2.13.02.14.0Patch全仓库范围内实施currency_code规范化PR #139752.15.x~2.16.xPatch主要跟随 framework 依赖更新2.17.2Patch为包添加 bugs 元数据PR #156832.18.0~2.20.1Patch连续跟随 medusajs/framework 依赖更新从上表可以清晰看到 store 模块的演进哲学小步快跑、紧跟框架。功能性的 breaking change 集中在 2.0.0整体重构、2.1.1DML 化和 2.12.3移除 default_locale几个节点。2.1.1 的里程碑迁移到 DML 声明模型CHANGELOG 中 2.1.1 条目PR #10467写明refactor: migrate store module to DML。这是 store 模块历史上最重要的一次内部重构。所谓 DMLData Model Language是 Medusa 框架提供的声明式数据建模 API由medusajs/framework/utils导出的model对象驱动。当前 store.ts 即为 DML 迁移后的产物import { model } from medusajs/framework/utils import StoreCurrency from ./currency import StoreLocale from ./locale const Store model .define(Store, { id: model.id({ prefix: store }).primaryKey(), name: model.text().default(Medusa Store).searchable(), default_sales_channel_id: model.text().nullable(), default_region_id: model.text().nullable(), default_location_id: model.text().nullable(), metadata: model.json().nullable(), supported_currencies: model.hasMany(() StoreCurrency, { mappedBy: store, }), supported_locales: model.hasMany(() StoreLocale, { mappedBy: store, }), }) .cascades({ delete: [supported_currencies, supported_locales], }) export default StoreDML 化的几个关键收益直接体现在这段代码中主键自动生成model.id({ prefix: store })声明 Store 表主键以store_前缀生成替代了手写 ID 生成逻辑字段类型语义化text()、json()、boolean()等取代了 ORM 层的类型注解框架据此自动推导数据库列类型关联关系声明式hasManymappedBy显式表达 Store 与 StoreCurrency、StoreLocale 的一对多关系级联行为内聚.cascades({ delete: [...] })声明删除 Store 时级联删除其支持的货币与语言环境避免手工在服务层编排删除顺序搜索能力标记name字段标记searchable()可供查询引擎建立索引。领域模型Store、StoreCurrency、StoreLocale 三实体DML 之后store 模块包含三个模型文件modelsStorestore 表如上所示Store 实体包含 6 个标量字段与 2 个一对多关联。注意name的默认值为Medusa Store这一默认值在 2.0 的初始迁移中同样体现——建表 SQL 使用name text not null default Medusa Store见 InitialSetup20240227075933.ts。StoreCurrencystore_currency 表const StoreCurrency model.define(StoreCurrency, { id: model.id({ prefix: stocur }).primaryKey(), currency_code: model.text().searchable(), is_default: model.boolean().default(false), store: model.belongsTo(() Store, { mappedBy: supported_currencies }).nullable(), })主键前缀stocurcurrency_code为可搜索的文本字段存放大写货币代码如EUR、USDis_default标识该货币是否为店铺默认货币默认false通过belongsTo反向声明对 Store 的多对一关系。StoreLocalestore_locale 表const StoreLocale model.define(StoreLocale, { id: model.id({ prefix: stloc }).primaryKey(), locale_code: model.text().searchable(), store: model.belongsTo(() Store, { mappedBy: supported_locales }).nullable(), })该模型标注since 2.12.3即它是在 2.12.3 引入的。locale_code采用 BCP 47 语言标签例如en-US、fr-FR。而 CHANGELOG 中 2.12.3 同时记录了Remove default_locale from StoreLocalePR #14300——结合当前模型定义可见StoreLocale 已不包含任何 default 语义字段仅保留id、locale_code、store三列这与模型源码完全一致。值得注意的细节是 Migration20251202184737.ts 中创建store_locale表的 SQL 仍包含is_default boolean not null default false列。从当前模型与服务实现来看该字段并未参与任何业务逻辑可推断这是迁移生成时序留下的历史痕迹从源码结构看迁移文件与模型定义存在快照时差实际行为以 DML 模型为准。服务层StoreModuleService 的能力与校验服务实现在 store-module-service.ts。它继承框架的MedusaService{ Store; StoreCurrency; StoreLocale }因此自动获得每个实体的 CRUD、列表、查询能力并在此之上实现了三个带业务语义的高层方法createStores创建店铺InjectManager() EmitEvents() async createStores( data: StoreTypes.CreateStoreDTO | StoreTypes.CreateStoreDTO[], MedusaContext() sharedContext: Context {} ): PromiseStoreTypes.StoreDTO | StoreTypes.StoreDTO[]内部调用create_依次执行normalizeInput输入规范化与validateCreateRequest请求校验最终通过storeService_.upsertWithReplace(..., { relations: [supported_currencies, supported_locales] })连同关联关系一次性写入。InjectManager()保证整个创建过程处于同一事务/工作单元中EmitEvents()使模块事件能够发布到事件总线。upsertStores不存在则建、存在则改protected async upsertStores_(data, sharedContext) { const input Array.isArray(data) ? data : [data] const forUpdate input.filter((store) !!store.id) const forCreate input.filter((store) !store.id) // forCreate - create_forUpdate - update_ }按是否携带id将输入拆分为待创建与待更新两组用promiseAll并行执行后合并结果。这是典型的 upsert 语义实现。updateStores按 id 或按条件更新async updateStores( id: string, data: UpdateStoreDTO, sharedContext? ): PromiseStoreDTO async updateStores( selector: FilterableStoreProps, data: UpdateStoreDTO, sharedContext? ): PromiseStoreDTO[]通过方法重载同时支持按单个 id 更新返回单个 DTO与按过滤条件批量更新先list出匹配记录再逐条组装输入返回 DTO 数组。输入规范化与业务校验normalizeInput与validateCreateRequest是两个值得展开的私有静态方法它们承载了模块的核心业务规则private static normalizeInputT extends StoreTypes.UpdateStoreDTO(stores: T[]): T[] { return stores.map((store) removeUndefined({ ...store, supported_currencies: store.supported_currencies?.map((c) ({ ...c, currency_code: normalizeCurrencyCode(c.currency_code), })), name: store.name?.trim(), }) ) }规范化做了两件事将currency_code统一转为小写这是 2.14.0 全仓库 currency 规范化的落点之一详见下文以及去除店铺名称首尾空白。validateCreateRequest则执行三层校验货币代码唯一性同一店铺的supported_currencies中不允许出现重复currency_code否则抛出Duplicate currency codes: ...默认货币唯一is_default: true的记录只能有一条多条时抛错Only one default currency is allowed必须有默认货币如果声明了supported_currencies但没有任何一条is_default则抛错There should be a default currency set for the store语言环境唯一性supported_locales中的locale_code同样不允许重复。这些校验规则正是店铺必须且有且仅有一个默认货币这一电商领域规则的直接体现且全部有 集成测试 覆盖。2.14.0 的关键修复currency_code 全局规范化CHANGELOG 中 2.14.0 的条目PR #13975记录了一次跨模块的大型修复fix(currency,payment,pricing,region,order,store,cart,core-flows,medusa,utils): repo wide currency_code normalization。这次修复横跨九个包store 模块是其中的一环。其底层实现在框架工具库 normalize-currency-code.tsexport function normalizeCurrencyCode(currencyCode: string) { if (!isString(currencyCode)) { throw new MedusaError(MedusaErrorTypes.INVALID_ARGUMENT, Currency code needs to be a string) } return currencyCode.toLowerCase() }规则很简单货币代码必须是字符串且统一转换为小写如EUR-eur。配套单测 normalize-currency-code.spec.ts 验证了大写输入被转小写的核心行为以及非法输入数字、undefined抛出INVALID_ARGUMENT异常。这次修复的动机在于不同模块历史上对货币代码的大小写处理不一致——有的写入时转小写有的直接透传导致跨模块 join 或查询时大小写不匹配、数据对不上。2.14.0 之后所有模块在写入前统一经过normalizeCurrencyCode。对 store 模块而言落点就是normalizeInput中对每个supported_currencies条目的currency_code调用该函数保证落库数据一律小写。这给使用者的直接启示是升级到 2.14.0 及以上后应避免再向接口传入大写currency_code并期望原样保存虽然框架会容错地将其小写化但依赖大写存储的旧数据需要在迁移时同步清洗。基础设施演进MikroORM 6、peer 依赖收敛与包元数据除了业务功能CHANGELOG 还记录了几次影响构建与运行时的基础设施变更它们对模块使用者的升级体验有直接影响。2.4.0升级到 MikroORM 6PR #10292 将整个仓库的 ORM 从 MikroORM 5 升级到 6。store 模块的迁移脚本体系package.json 中的migration:create、migration:up等均基于medusa-mikro-ormCLI 封装因此 ORM 大版本升级会直接改变迁移文件的生成格式与运行时行为。2.0.5 条目PR #10119Fix/mikro orm cli wrapper修复的正是该 CLI 包装器属于本次升级的前置修复。2.6.1移除 Medusa 包间的版本范围依赖PR #11738 移除了 Medusa 各包对彼此的 range 版本约束。在此之前包间依赖可能写成^2.0.0这类范围导致多包组合安装时版本漂移、行为不一致。移除 range 后store 模块与medusajs/framework的依赖改为精确版本——当前 package.json 中medusajs/framework: 2.20.1正是这一策略的体现devDependencies 与 peerDependencies 均为精确 2.20.1。2.11.0peer 依赖收敛到 framework 单一包PR #13439 将分散在各模块中的 peer 依赖统一移入单一包并从medusajs/framework重新导出。对 store 模块的影响是其源码中所有来自medusajs/framework/types、medusajs/framework/utils、medusajs/framework/mikro-orm/migrations的导入均指向这唯一的依赖源参见模型、服务、迁移文件顶部的 import 语句外部使用者也只需保证项目中存在匹配版本的 framework 即可。2.17.2补充 bugs 元数据PR #15683 为包补充了bugs元数据对应 package.json 中的bugs: { url: https://github.com/medusajs/medusa/issues }。这是 npm 包发布规范的细节完善便于使用者一键跳转提交 issue。数据库迁移从 2.0 遗留表结构到 store_localestore 模块的迁移目录migrations保留了从 Medusa 2.0 到现在的完整建表/改表历史InitialSetup20240227075933.ts2.0 时代的初始迁移。它带有典型的兼容旧版本逻辑——若已存在store表则执行alter table系列语句列类型改为 text、补充default_region_id、deleted_at、supported_currency_codes等新列、删除旧外键约束否则直接create table if not exists全新建表。文件中被注释掉的drop column default_currency_code等语句提示2.0 在数据搬迁上采取了保守策略旧列暂缓删除以避免数据丢失Migration20240621145944.ts / Migration20241206083313.ts2.0 早期至 2.12 之间的增量调整Migration20251202184737.ts创建store_locale表对应 2.12.3 引入的supported_locales能力。建表 SQL 为locale_code建立索引、store_id建立外键on update cascade on delete cascade并提供down()回滚drop table ... cascadeMigration20251212161429.ts最近的增量迁移。对于使用者升级策略上需要关注的是升级到包含 store_locale 的版本 2.12.3时应用启动会自动执行新增迁移创建该表无需手工干预但若有自定义迁移脚本叠加需注意外键依赖顺序。集成测试模块行为的验证基准模块的集成测试 store-module-service.spec.ts 基于moduleIntegrationTestRunner在真实数据库环境下运行是理解模块行为最直接的参考。测试覆盖linkable 配置断言模块导出store、storeCurrency、storeLocale三个可链接实体及其 linkable 字段名store_id、store_currency_id、store_locale_id创建店铺通过createStores(createStoreFixture)创建包含两个货币eur、usd、两个语言环境fr-FR、en-US、默认销售渠道与区域、metadata 的完整店铺并断言结果对象结构后续用例继续覆盖 upsert、更新、过滤查询等路径该文件共 223 行。测试夹具fixtures/index.ts 提供了标准化的createStoreFixture如果你要为 store 模块编写自己的集成测试可以直接复用该夹具结构。升级路线与实践建议综合 CHANGELOG 与源码对使用 store 模块的开发者建议按以下路线评估升级确认框架版本匹配store 模块与medusajs/framework精确版本绑定升级时必须同步升级 framework 到相同版本如 2.20.1否则 peerDependencies 校验会失败2.12.3 的语义变化StoreLocale不再有default_locale字段如果你依赖该字段做默认语言判断需要改用其他方式例如结合 store 的默认区域或应用层配置2.14.0 的大小写约束currency_code一律小写存储检查历史数据中是否存在大写货币代码并规划清洗脚本数据库迁移 2.12.3 的版本会自动创建store_locale表若使用共享数据库注意迁移时序与并发迁移的锁冲突Node 版本模块要求 Node.js 20升级前确认运行时环境满足条件。小结从 CHANGELOG 反推实现可以看到medusajs/store模块是一条基础设施优先的演进曲线2.0.0 奠定模块化骨架2.1.1 完成 DML 声明式建模2.4.0 跟随 MikroORM 62.11.0 收敛依赖2.12.3 引入supported_locales并移除default_locale2.14.0 落实currency_code全局小写规范此后进入依赖跟随的稳定期。理解这条曲线不仅有助于安全升级也能让你在阅读 DML 模型、StoreModuleService与迁移脚本时清楚每一行代码背后的设计意图。【免费下载链接】medusaThe worlds most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考