Files
shrimp-server/src/MemoryShrimpCrud.ts
T
wc24 8e4d5620b7 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'。
2026-05-23 16:47:18 +08:00

127 lines
4.5 KiB
TypeScript

import { ShrimpCrud } from './Shrimp'
import { IShrimpQuery, IShrimpCondition, IShrimpPopulate, IPageResult, isShrimpCondition } from './IShrimpType'
export class MemoryShrimpCrud<T extends { _id: string }> implements ShrimpCrud<T> {
private data: Map<string, T> = new Map()
private autoId: number = 1
private isCondition = isShrimpCondition
private matchCondition(item: any, key: string, cond: IShrimpCondition | any): boolean {
const v = item[key]
if (this.isCondition(cond)) {
if (cond.eq !== undefined && v !== cond.eq) return false
if (cond.in !== undefined && !cond.in.includes(v)) return false
if (cond.like !== undefined && !String(v ?? '').toLowerCase().includes(String(cond.like).toLowerCase())) return false
if (cond.gte !== undefined && v < cond.gte) return false
if (cond.lte !== undefined && v > cond.lte) return false
return true
}
return v === cond
}
async findOne(query?: any): Promise<T | null> {
if (!query) return null
for (const item of this.data.values()) {
if (Object.keys(query).every(key => (item as any)[key] === query[key])) {
return item
}
}
return null
}
async list(query?: IShrimpQuery): Promise<IPageResult<T>> {
let items = Array.from(this.data.values())
// where 条件 (AND)
if (query?.where) {
for (const [key, cond] of Object.entries(query.where)) {
items = items.filter(item => this.matchCondition(item, key, cond))
}
}
// orWhere 条件 (OR 组之间)
if (query?.orWhere && query.orWhere.length > 0) {
items = items.filter(item =>
query.orWhere!.some(group =>
Object.entries(group).every(([key, cond]) => this.matchCondition(item, key, cond))
)
)
}
const total = items.length
// 排序
if (query?.order) {
for (const [key, dir] of query.order) {
const d = dir === 'desc' ? -1 : 1
items.sort((a, b) => {
const va = (a as any)[key], vb = (b as any)[key]
if (va == null && vb == null) return 0
if (va == null) return 1
if (vb == null) return -1
if (va < vb) return -d
if (va > vb) return d
return 0
})
}
}
// 分页
const offset = query?.offset || 0
const limit = query?.limit ?? items.length
const list = items.slice(offset, offset + limit)
const page = Math.floor(offset / limit) + 1
const pageSize = limit
const totalPages = Math.ceil(total / pageSize)
return { list, total, page, pageSize, totalPages }
}
async populateItems(items: any[], populates: IShrimpPopulate[], crudRegistry?: Map<string, ShrimpCrud<any>>): Promise<void> {
if (!crudRegistry) return
for (const pop of populates) {
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]]))
for (const item of items) {
const val = (item as any)[pop.key]
if (Array.isArray(val)) {
(item as any)[`${pop.key}_label`] = val.map((id: any) => labelMap.get(String(id)) ?? null)
} else if (val != null) {
(item as any)[`${pop.key}_label`] = labelMap.get(String(val)) ?? null
}
}
}
}
async get(id: string): Promise<T | null> {
return this.data.get(id) || null
}
async add(item: Omit<T, '_id'>): Promise<T> {
const id = (this.autoId++).toString()
const newItem = { _id: id, ...item } as T
this.data.set(id, newItem)
return newItem
}
async edit(id: string, item: Partial<T>): Promise<T | null> {
const old = this.data.get(id)
if (!old) return null
const updated = { ...old, ...item }
this.data.set(id, updated)
return updated
}
async delete(id: string): Promise<boolean> {
return this.data.delete(id)
}
async clear(): Promise<boolean> {
this.data.clear()
return true
}
}