feat!: 拆分 core / server 入口,peer deps 全部 optional

让只用 Shrimp 核心(RPC + 模块声明 + 内存 CRUD)的项目不必装
express / mongoose / bcrypt / jsonwebtoken,显著降低 install 时间。

- src/core.ts:0 重依赖入口(Shrimp / MemoryShrimpCrud / 类型)
- src/server.ts:在 core 之上再导出 ShrimpServer + MongoShrimpCrud
- package.json:加 exports 字段 + peerDependenciesMeta.optional
- Shrimp.ts:bcrypt 改 lazy import,只有走 Password 字段才触发
- MemoryShrimpCrud.populateItems:接 crudRegistry 参数(对齐 Mongo)
- ShrimpTableModule 构造时自动登记 crudRegistry,业务无需手动 wire
- 新增 test/crudRegistry.test.ts 验证自动登记 + relation 自动 populate

BREAKING: import { ShrimpServer } from 'shrimp-server' 不再可用,
请改为 'shrimp-server/server'。
This commit is contained in:
2026-05-23 16:47:18 +08:00
parent 40f311796b
commit 8e4d5620b7
11 changed files with 207 additions and 99 deletions
+3 -4
View File
@@ -4,8 +4,6 @@ import { IShrimpQuery, IShrimpCondition, IShrimpPopulate, IPageResult, isShrimpC
export class MemoryShrimpCrud<T extends { _id: string }> implements ShrimpCrud<T> {
private data: Map<string, T> = new Map()
private autoId: number = 1
/** 获取关联模块的数据源(用于 populate) */
getRelatedCrud?: (moduleKey: string) => ShrimpCrud<any> | undefined
private isCondition = isShrimpCondition
@@ -80,9 +78,10 @@ export class MemoryShrimpCrud<T extends { _id: string }> implements ShrimpCrud<T
return { list, total, page, pageSize, totalPages }
}
async populateItems(items: any[], populates: IShrimpPopulate[]): Promise<void> {
async populateItems(items: any[], populates: IShrimpPopulate[], crudRegistry?: Map<string, ShrimpCrud<any>>): Promise<void> {
if (!crudRegistry) return
for (const pop of populates) {
const relatedCrud = this.getRelatedCrud?.(pop.moduleKey)
const relatedCrud = crudRegistry.get(pop.moduleKey)
if (!relatedCrud) continue
const allRelated = (await relatedCrud.list({})).list
const labelMap = new Map(allRelated.map(item => [String(item._id), (item as any)[pop.labelKey]]))
+10 -5
View File
@@ -5,9 +5,14 @@
import { ShrimpCode, IShrimpResponse, ITableCrudProto, IModuleInfo, IUser, IRole, WithId, IRoleProto, IPageQuery, IPageResult, IFieldConfig, IFieldRule, IFieldTypeHandler, IShrimpQuery, IShrimpCondition, IShrimpPopulate, ShrimpConfig, IShrimpOperator, IMenuItem, ShrimpError, ShrimpFieldTypes, ILabel } from "./IShrimpType"
import * as bcrypt from "bcrypt"
import { AsyncLocalStorage } from 'async_hooks'
// 懒加载 bcrypt: 只有走到 Password 字段 hash 或 verifyPassword 才 import,
// 让纯核心(MemoryShrimpCrud + 业务模块)的消费方可以不装 bcrypt
async function loadBcrypt(): Promise<typeof import("bcrypt")> {
return await import("bcrypt")
}
export class Shrimp {
root: Shrimp = this
modules: Map<string, ShrimpModule> = new Map()
@@ -253,6 +258,7 @@ export const defaultTypeHandlers: Record<string, IFieldTypeHandler> = {
[ShrimpFieldTypes.Password]: {
reqFilter: async (value) => {
if (typeof value === 'string' && value.length > 0) {
const bcrypt = await loadBcrypt()
return bcrypt.hash(value, 10)
}
return value
@@ -318,10 +324,8 @@ export class ShrimpTableModule<T, P extends ITableCrudProto<T> = ITableCrudProto
if (fields) {
this.fields = fields
}
// 自动注册 CRUD 到 root.crudRegistry,供 MongoShrimpCrud.populateItems 自动解析 relation
if ((dataSource as any).model) {
this.root.crudRegistry.set(moduleKey, dataSource)
}
// 自动注册 CRUD 到 root.crudRegistry,populateItems 据此解析 relation
this.root.crudRegistry.set(moduleKey, dataSource)
}
getFields(): IFieldConfig[] {
@@ -711,6 +715,7 @@ export class UserModule extends ShrimpTableModule<WithId<IUser>> {
/** 校验密码,子类可覆盖 */
protected async verifyPassword(storedPassword: string, plainPassword: string): Promise<boolean> {
const bcrypt = await loadBcrypt()
return bcrypt.compare(plainPassword, storedPassword)
}
+78
View File
@@ -0,0 +1,78 @@
/**
* Shrimp 核心入口 — 0 重依赖
*
* 只暴露 Shrimp 框架本体 + 内存数据源 + 类型定义,不引入
* express / mongoose / jsonwebtoken。bcrypt 是 lazy import,
* 不走 Password 字段就不会触发。
*
* 适用场景:
* - 只想用 Shrimp 的 RPC dispatch + 模块/字段声明 + 内存 CRUD
* - 自己接 HTTP / 自己写存储层
* - 跑测试 / 跑无 DB 的 demo
*
* 需要完整后台(Express + Mongoose + JWT)请从 'shrimp-server/server' 导入。
*/
// ===== 类型 / 接口 / 枚举 =====
export {
ShrimpCode,
ShrimpError,
ShrimpFieldTypes,
ShrimpConfig,
isShrimpCondition,
} from "./IShrimpType"
export type {
ILabel,
IShrimpResponse,
IModuleInfo,
ILoginResult,
IMeResult,
IMenuItem,
ITableProto,
ITableCrudProto,
IUser,
IRole,
IOperationLog,
WithId,
IShrimpOperator,
IShrimpPowerData,
ShrimpRequestFn,
IShrimpClient,
ShrimpFieldType,
FieldEditable,
IFieldTypeHandler,
IFieldConfig,
IFieldRule,
IPageQuery,
IPageResult,
IShrimpCondition,
IShrimpPopulate,
IShrimpQuery,
IPowerItem,
IUserProto,
IRoleProto,
} from "./IShrimpType"
// ===== 框架核心 =====
export {
Shrimp,
ShrimpModule,
ShrimpTableModule,
ShrimpPower,
RootShrimpPower,
UserModule,
RoleModule,
defaultTypeHandlers,
} from "./Shrimp"
export type {
IShrimpModuleOptions,
ShrimpCrud,
} from "./Shrimp"
// ===== 数据源(纯内存,无外部依赖) =====
export { MemoryShrimpCrud } from "./MemoryShrimpCrud"
// ===== 可选模块(纯框架代码,无外部依赖) =====
export { OperationLogModule } from "./OperationLogModule"
+7 -75
View File
@@ -1,78 +1,10 @@
/**
* Shrimp Server — 公共 API
* 默认入口 = 核心 (0 重依赖)
*
* 内部实现细节(如 ShrimpEvent)未在此处导出,业务请只依赖本文件再导出的符号。
* - import ... from 'shrimp-server' => 这个文件 / src/core.ts(纯框架,无 peer)
* - import ... from 'shrimp-server/server' => src/server.ts(再带 ShrimpServer + Mongo)
*
* 该文件存在的意义是兼容不识别 `package.json#exports` 的旧工具链(npm < 7、
* 部分 bundler 的 legacy resolve),让 `main: src/index.ts` 也能正常工作。
*/
// ===== 类型 / 接口 / 枚举 =====
export {
ShrimpCode,
ShrimpError,
ShrimpFieldTypes,
ShrimpConfig,
isShrimpCondition,
} from "./IShrimpType"
export type {
ILabel,
IShrimpResponse,
IModuleInfo,
ILoginResult,
IMeResult,
IMenuItem,
ITableProto,
ITableCrudProto,
IUser,
IRole,
IOperationLog,
WithId,
IShrimpOperator,
IShrimpPowerData,
ShrimpRequestFn,
IShrimpClient,
ShrimpFieldType,
FieldEditable,
IFieldTypeHandler,
IFieldConfig,
IFieldRule,
IPageQuery,
IPageResult,
IShrimpCondition,
IShrimpPopulate,
IShrimpQuery,
IPowerItem,
IUserProto,
IRoleProto,
} from "./IShrimpType"
// ===== 框架核心 =====
export {
Shrimp,
ShrimpModule,
ShrimpTableModule,
ShrimpPower,
RootShrimpPower,
UserModule,
RoleModule,
defaultTypeHandlers,
} from "./Shrimp"
export type {
IShrimpModuleOptions,
ShrimpCrud,
} from "./Shrimp"
// ===== 服务端实现(Express + Mongoose + JWT) =====
export {
ShrimpServer,
UserSchema,
RoleSchema,
OperationLogSchema,
} from "./ShrimpServer"
// ===== 数据源 =====
export { MongoShrimpCrud } from "./MongoShrimpCrud"
export { MemoryShrimpCrud } from "./MemoryShrimpCrud"
// ===== 可选模块 =====
export { OperationLogModule } from "./OperationLogModule"
export * from "./core"
+25
View File
@@ -0,0 +1,25 @@
/**
* Shrimp 服务端入口 — 包含 Express + Mongoose + JWT 实现
*
* 在 core 之上额外暴露:
* - ShrimpServer:Express Router + JWT 鉴权 + Mongo 连接
* - MongoShrimpCrud:Mongoose 数据源
* - 内置 User/Role/OperationLog 的 Mongoose Schema
*
* 这一入口需要消费方安装 peer 依赖:express / mongoose / jsonwebtoken / bcrypt。
* 不需要这些时请改从 'shrimp-server' 导入,不会触发任何 peer。
*/
// 再导出所有 core 的符号,'shrimp-server/server' 单一入口就够用
export * from "./core"
// ===== 服务端实现(Express + Mongoose + JWT) =====
export {
ShrimpServer,
UserSchema,
RoleSchema,
OperationLogSchema,
} from "./ShrimpServer"
// ===== Mongoose 数据源 =====
export { MongoShrimpCrud } from "./MongoShrimpCrud"