init: 从 cs-hand/hand-server-2/lib/shrimp 抽出独立仓
- 6 个框架源文件搬到 src/, 加 src/index.ts barrel 作为公共 API 入口 - package.json 标 private, main/types 指 src/index.ts (ts-node 消费方编译) - 加 vitest + 22 条单元测试 (ILabel / MemoryShrimpCrud) 全通过 - examples/basic-admin/ 用 MemoryShrimpCrud 跑零 Mongo 依赖 demo 待办: ShrimpServer 强耦合 Mongo+JWT+Express, crudRegistry 用 .model 判断数据源, 这两个问题在 demo 中已暴露, 计划后续重构补到 90 分。
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
# basic-admin demo
|
||||
|
||||
零外部依赖的 Shrimp demo,**只用内存 CRUD,不依赖 MongoDB**。
|
||||
|
||||
证明:Shrimp 的核心(模块注册 / RPC dispatch / 字段配置)在没有 mongoose、没有 JWT、没有用户表的情况下也能跑。
|
||||
|
||||
## 起服务
|
||||
|
||||
从 demo 目录起(本地链 shrimp-server):
|
||||
|
||||
```sh
|
||||
cd examples/basic-admin
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
或者从仓库根目录直接 `npm run demo`(由根 package.json 提供)。
|
||||
|
||||
服务起在 `http://localhost:3100`。
|
||||
|
||||
## 试一下
|
||||
|
||||
```sh
|
||||
# 列商品
|
||||
curl -s -X POST http://localhost:3100/api/product/list \
|
||||
-H 'content-type: application/json' -d '[{}]' | jq
|
||||
|
||||
# 列标签
|
||||
curl -s -X POST http://localhost:3100/api/tag/list \
|
||||
-H 'content-type: application/json' -d '[{}]' | jq
|
||||
|
||||
# 新增商品
|
||||
curl -s -X POST http://localhost:3100/api/product/add \
|
||||
-H 'content-type: application/json' \
|
||||
-d '[{"code":"P003","name":"橙子","price":4,"tagIds":[]}]' | jq
|
||||
|
||||
# 改某个商品
|
||||
curl -s -X POST http://localhost:3100/api/product/edit \
|
||||
-H 'content-type: application/json' -d '["1",{"price":99}]' | jq
|
||||
|
||||
# 删除商品
|
||||
curl -s -X POST http://localhost:3100/api/product/delete \
|
||||
-H 'content-type: application/json' -d '["1"]' | jq
|
||||
```
|
||||
|
||||
请求体永远是参数数组,按位置传给事件 handler。
|
||||
|
||||
## 这个 demo 暴露了哪些 70→90 的问题
|
||||
|
||||
- **`Shrimp.ts:322` 的 `if ((dataSource as any).model)`** 让 MemoryShrimpCrud 不能注册到 `crudRegistry`,relation populate 自动联表对内存数据源不生效
|
||||
- **没有现成的 RPC mount helper** — 这段 `app.post("/api/*splat", ...)` 应该是框架提供的,而不是 demo 里手写
|
||||
- **要登录 / 鉴权要么自己写,要么用 ShrimpServer 强吃 Mongo+JWT**,中间没有"我只想要 RPC + 模块,自己接 auth"这条路径
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Shrimp basic-admin demo
|
||||
*
|
||||
* 用 MemoryShrimpCrud 跑一个零依赖的 admin server,展示:
|
||||
* - Shrimp 核心(模块注册 / RPC dispatch / 权限树)
|
||||
* - ShrimpTableModule + MemoryShrimpCrud 的 CRUD 自动事件
|
||||
* - 通过 Express 暴露成 HTTP RPC
|
||||
*
|
||||
* 不使用 ShrimpServer/Mongoose/JWT,刻意暴露 ShrimpServer 把太多东西耦合在
|
||||
* Express+Mongo+Auth 一起的事实,作为后续重构的对照样本。
|
||||
*/
|
||||
import express from "express"
|
||||
import {
|
||||
Shrimp,
|
||||
ShrimpTableModule,
|
||||
MemoryShrimpCrud,
|
||||
ShrimpFieldTypes,
|
||||
ShrimpError,
|
||||
ShrimpCode,
|
||||
} from "shrimp-server"
|
||||
import type { IFieldConfig, WithId } from "shrimp-server"
|
||||
|
||||
interface ITag { _id: string; name: string }
|
||||
interface IProduct { _id: string; code: string; name: string; price: number; tagIds: string[] }
|
||||
|
||||
const tagFields: IFieldConfig[] = [
|
||||
{ key: "name", label: "标签名", type: ShrimpFieldTypes.String, required: true, searchable: true },
|
||||
]
|
||||
|
||||
const productFields: IFieldConfig[] = [
|
||||
{ key: "code", label: "编码", type: ShrimpFieldTypes.String, required: true, editable: "addOnly" },
|
||||
{ key: "name", label: "名称", type: ShrimpFieldTypes.String, required: true, searchable: true },
|
||||
{ key: "price", label: "价格", type: ShrimpFieldTypes.Number, required: true },
|
||||
{
|
||||
key: "tagIds", label: "标签", type: ShrimpFieldTypes.Relation,
|
||||
relation: { moduleKey: "tag", labelKey: "name" }, multiple: true, defaultValue: [],
|
||||
},
|
||||
]
|
||||
|
||||
async function bootstrap() {
|
||||
const shrimp = new Shrimp()
|
||||
|
||||
const tagCrud = new MemoryShrimpCrud<WithId<ITag>>()
|
||||
new ShrimpTableModule(shrimp, "tag", tagCrud, tagFields, "标签管理").initCrud()
|
||||
|
||||
const productCrud = new MemoryShrimpCrud<WithId<IProduct>>()
|
||||
new ShrimpTableModule(shrimp, "product", productCrud, productFields, "商品管理").initCrud()
|
||||
|
||||
// 种子数据
|
||||
const t1 = await tagCrud.add({ name: "热门" } as any)
|
||||
const t2 = await tagCrud.add({ name: "促销" } as any)
|
||||
await productCrud.add({ code: "P001", name: "苹果", price: 5, tagIds: [t1._id] } as any)
|
||||
await productCrud.add({ code: "P002", name: "香蕉", price: 3, tagIds: [t1._id, t2._id] } as any)
|
||||
|
||||
const app = express()
|
||||
app.use(express.json())
|
||||
|
||||
// 简化的 RPC dispatch: POST /api/<...path> body 是参数数组
|
||||
app.post(/^\/api\/.+/, async (req, res) => {
|
||||
const path = req.path.replace(/^\/api\//, "").split("/").filter(Boolean)
|
||||
const args = Array.isArray(req.body) ? req.body : []
|
||||
try {
|
||||
const data = await shrimp.call(path, ...args)
|
||||
res.json({ code: ShrimpCode.OK, data })
|
||||
} catch (e: any) {
|
||||
if (e instanceof ShrimpError) {
|
||||
res.json({ code: e.code, tip: e.tip })
|
||||
} else {
|
||||
console.error(e)
|
||||
res.json({ code: ShrimpCode.SERVER_ERROR, log: String(e?.message || e) })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const port = 3100
|
||||
app.listen(port, () => {
|
||||
console.log(`Shrimp basic-admin demo: http://localhost:${port}`)
|
||||
console.log("")
|
||||
console.log("# 试试:")
|
||||
console.log(` curl -s -X POST http://localhost:${port}/api/product/list -H 'content-type: application/json' -d '[{}]' | jq`)
|
||||
console.log(` curl -s -X POST http://localhost:${port}/api/tag/list -H 'content-type: application/json' -d '[{}]' | jq`)
|
||||
console.log(` curl -s -X POST http://localhost:${port}/api/product/add -H 'content-type: application/json' -d '[{"code":"P003","name":"橙子","price":4,"tagIds":[]}]' | jq`)
|
||||
})
|
||||
}
|
||||
|
||||
bootstrap().catch(err => {
|
||||
console.error("bootstrap failed:", err)
|
||||
process.exit(1)
|
||||
})
|
||||
Generated
+1516
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "shrimp-basic-admin-example",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "tsx index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"shrimp-server": "file:../..",
|
||||
"express": "^4.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user