让只用 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'。
134 lines
5.1 KiB
TypeScript
134 lines
5.1 KiB
TypeScript
import { describe, it, expect, beforeEach } from "vitest"
|
|
import { MemoryShrimpCrud } from "../src"
|
|
|
|
interface IItem {
|
|
_id: string
|
|
name: string
|
|
price: number
|
|
tags?: string[]
|
|
}
|
|
|
|
describe("MemoryShrimpCrud", () => {
|
|
let crud: MemoryShrimpCrud<IItem>
|
|
|
|
beforeEach(() => {
|
|
crud = new MemoryShrimpCrud<IItem>()
|
|
})
|
|
|
|
it("add 返回带 _id 的新记录", async () => {
|
|
const item = await crud.add({ name: "苹果", price: 5 } as any)
|
|
expect(item._id).toBeDefined()
|
|
expect(item.name).toBe("苹果")
|
|
expect(item.price).toBe(5)
|
|
})
|
|
|
|
it("get 按 id 取记录", async () => {
|
|
const a = await crud.add({ name: "A", price: 1 } as any)
|
|
const fetched = await crud.get(a._id)
|
|
expect(fetched).toEqual(a)
|
|
})
|
|
|
|
it("get 不存在返回 null", async () => {
|
|
expect(await crud.get("missing")).toBeNull()
|
|
})
|
|
|
|
it("edit 部分更新", async () => {
|
|
const a = await crud.add({ name: "A", price: 1 } as any)
|
|
const updated = await crud.edit(a._id, { price: 99 })
|
|
expect(updated?.price).toBe(99)
|
|
expect(updated?.name).toBe("A")
|
|
})
|
|
|
|
it("delete 删除返回 true,再次删返回 false", async () => {
|
|
const a = await crud.add({ name: "A", price: 1 } as any)
|
|
expect(await crud.delete(a._id)).toBe(true)
|
|
expect(await crud.delete(a._id)).toBe(false)
|
|
expect(await crud.get(a._id)).toBeNull()
|
|
})
|
|
|
|
it("list 全量返回 + 分页字段", async () => {
|
|
await crud.add({ name: "A", price: 1 } as any)
|
|
await crud.add({ name: "B", price: 2 } as any)
|
|
const result = await crud.list()
|
|
expect(result.total).toBe(2)
|
|
expect(result.list).toHaveLength(2)
|
|
})
|
|
|
|
it("list where eq 过滤", async () => {
|
|
await crud.add({ name: "A", price: 1 } as any)
|
|
await crud.add({ name: "B", price: 2 } as any)
|
|
const result = await crud.list({ where: { name: { eq: "B" } } })
|
|
expect(result.total).toBe(1)
|
|
expect(result.list[0].name).toBe("B")
|
|
})
|
|
|
|
it("list where like 模糊匹配", async () => {
|
|
await crud.add({ name: "apple-juice", price: 1 } as any)
|
|
await crud.add({ name: "orange", price: 2 } as any)
|
|
const result = await crud.list({ where: { name: { like: "APP" } } })
|
|
expect(result.total).toBe(1)
|
|
expect(result.list[0].name).toBe("apple-juice")
|
|
})
|
|
|
|
it("list where gte/lte 范围过滤", async () => {
|
|
for (let i = 1; i <= 5; i++) await crud.add({ name: `n${i}`, price: i } as any)
|
|
const result = await crud.list({ where: { price: { gte: 2, lte: 4 } } })
|
|
expect(result.total).toBe(3)
|
|
})
|
|
|
|
it("list order 排序", async () => {
|
|
await crud.add({ name: "B", price: 2 } as any)
|
|
await crud.add({ name: "A", price: 1 } as any)
|
|
await crud.add({ name: "C", price: 3 } as any)
|
|
const asc = await crud.list({ order: [["price", "asc"]] })
|
|
expect(asc.list.map(i => i.price)).toEqual([1, 2, 3])
|
|
const desc = await crud.list({ order: [["price", "desc"]] })
|
|
expect(desc.list.map(i => i.price)).toEqual([3, 2, 1])
|
|
})
|
|
|
|
it("list offset/limit 分页", async () => {
|
|
for (let i = 1; i <= 10; i++) await crud.add({ name: `n${i}`, price: i } as any)
|
|
const page2 = await crud.list({ offset: 3, limit: 3, order: [["price", "asc"]] })
|
|
expect(page2.list.map(i => i.price)).toEqual([4, 5, 6])
|
|
expect(page2.total).toBe(10)
|
|
expect(page2.totalPages).toBe(4)
|
|
})
|
|
|
|
it("findOne 按字段查找", async () => {
|
|
await crud.add({ name: "alice", price: 1 } as any)
|
|
await crud.add({ name: "bob", price: 2 } as any)
|
|
const found = await crud.findOne({ name: "bob" })
|
|
expect(found?.price).toBe(2)
|
|
})
|
|
|
|
it("clear 清空所有记录", async () => {
|
|
await crud.add({ name: "A", price: 1 } as any)
|
|
await crud.add({ name: "B", price: 2 } as any)
|
|
await crud.clear()
|
|
const result = await crud.list()
|
|
expect(result.total).toBe(0)
|
|
})
|
|
|
|
it("populateItems 旁挂 _label (通过 crudRegistry 解析)", async () => {
|
|
const tagCrud = new MemoryShrimpCrud<{ _id: string; name: string }>()
|
|
await tagCrud.add({ name: "热门" } as any)
|
|
await tagCrud.add({ name: "促销" } as any)
|
|
|
|
const itemCrud = new MemoryShrimpCrud<IItem>()
|
|
const a = await itemCrud.add({ name: "苹果", price: 5, tags: ["1", "2"] } as any)
|
|
|
|
const registry = new Map<string, any>([["tag", tagCrud]])
|
|
const items = [{ ...a }] as any[]
|
|
await itemCrud.populateItems(items, [{ moduleKey: "tag", key: "tags", labelKey: "name" }], registry)
|
|
expect(items[0].tags_label).toEqual(["热门", "促销"])
|
|
})
|
|
|
|
it("populateItems 不传 crudRegistry 静默跳过(不抛错,不旁挂)", async () => {
|
|
const itemCrud = new MemoryShrimpCrud<IItem>()
|
|
const a = await itemCrud.add({ name: "苹果", price: 5, tags: ["1"] } as any)
|
|
const items = [{ ...a }] as any[]
|
|
await itemCrud.populateItems(items, [{ moduleKey: "tag", key: "tags", labelKey: "name" }])
|
|
expect(items[0].tags_label).toBeUndefined()
|
|
})
|
|
})
|