@dtdyq/restbase 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # RestBase
2
+
3
+ > 零代码通用 REST 服务 — 连接 SQLite / MySQL,自动将数据表通过 REST API 暴露。
4
+
5
+ ## 特性
6
+
7
+ - **零代码 CRUD** — 自动分析表结构,生成完整 REST 接口
8
+ - **租户隔离** — `owner` 字段自动按用户隔离数据,支持 NULL 公开模式
9
+ - **双鉴权** — JWT + Basic Auth 开箱即用
10
+ - **两套查询** — URL 参数(CLI 调试)+ POST Body(前端推荐),支持复杂 WHERE / SELECT / ORDER / GROUP / 分页
11
+ - **类型安全客户端** — 零依赖 TypeScript 客户端,链式 API,编译期推导 SELECT 返回类型
12
+ - **静态文件托管** — 可选挂载前端打包产物,API 与前端同端口
13
+ - **结构化日志** — pino + 文件滚动 + 请求追踪 ID
14
+ - **单文件部署** — `bun build --compile` 编译为独立二进制,支持交叉编译
15
+
16
+ ## 快速开始
17
+
18
+ ```bash
19
+ bun install
20
+ bun run server.ts
21
+ ```
22
+
23
+ ```bash
24
+ # 健康检查
25
+ curl http://localhost:3333/api/health
26
+
27
+ # Basic Auth 查询
28
+ curl -u admin:admin http://localhost:3333/api/data/products
29
+ ```
30
+
31
+ ## 构建与部署
32
+
33
+ ```bash
34
+ bun run build # 当前平台
35
+ bun run build:linux # Linux x64
36
+ bun run build:linux-arm # Linux ARM64
37
+ bun run build:mac # macOS x64
38
+ bun run build:mac-arm # macOS ARM64
39
+ bun run build:windows # Windows x64
40
+
41
+ # 运行二进制(无需 Bun 运行时)
42
+ ./restbase
43
+ ```
44
+
45
+ ## 环境变量
46
+
47
+ | 变量 | 说明 | 默认值 |
48
+ |:--------------------------|:---------------------------|:--------------------|
49
+ | `SVR_PORT` | 服务端口 | `3333` |
50
+ | `SVR_STATIC` | 静态文件目录(前端托管) | 空 |
51
+ | `SVR_API_LIMIT` | API 限流(每秒每接口请求数) | `100` |
52
+ | `DB_URL` | 数据库连接串 | `sqlite://:memory:` |
53
+ | `DB_AUTH_TABLE` | 用户表名 | `users` |
54
+ | `DB_AUTH_FIELD` | owner 字段名 | `owner` |
55
+ | `DB_AUTH_FIELD_NULL_OPEN` | owner=NULL 视为公开 | `false` |
56
+ | `DB_INIT_SQL` | 启动时执行的 SQL 文件 | 空 |
57
+ | `AUTH_JWT_SECRET` | JWT 密钥 | `restbase` |
58
+ | `AUTH_JWT_EXP` | JWT 过期秒数 | `43200` |
59
+ | `AUTH_BASIC_OPEN` | 开启 Basic Auth | `true` |
60
+ | `LOG_LEVEL` | `ERROR` / `INFO` / `DEBUG` | `INFO` |
61
+ | `LOG_CONSOLE` | 控制台输出 | `true` |
62
+ | `LOG_FILE` | 日志文件路径 | 空 |
63
+ | `LOG_RETAIN_DAYS` | 日志保留天数 | `7` |
64
+
65
+ ## 测试
66
+
67
+ ```bash
68
+ bun test rest.test.ts # 137+ 用例
69
+ ```
70
+
71
+ ## 文档
72
+
73
+ | 文档 | 内容 |
74
+ |:-------------------------------------------------|:--------------------------------------------------|
75
+ | [documents/server.md](documents/server.md) | 服务端详细文档 — 配置、全部 API 接口说明与示例、日志、部署 |
76
+ | [documents/client.md](documents/client.md) | 前端客户端文档 — 安装、API 速查、QueryBuilder 链式调用、类型安全 SELECT |
77
+ | [documents/db_design.md](documents/db_design.md) | 数据库设计指南 — 表结构规范、约束、索引、设计模式与检查清单 |
78
+ | [documents/design.md](documents/design.md) | 需求与设计文档 — 架构设计、技术规格、完整接口定义 |
79
+
80
+ ## 文件结构
81
+
82
+ ```
83
+ restbase/
84
+ ├── server.ts # 入口
85
+ ├── types.ts # 配置 + 类型
86
+ ├── db.ts # 数据库
87
+ ├── auth.ts # 鉴权
88
+ ├── crud.ts # CRUD 路由
89
+ ├── query.ts # SQL 生成
90
+ ├── logger.ts # 日志
91
+ ├── client/
92
+ │ └── restbase-client.ts # 前端客户端
93
+ ├── documents/
94
+ │ ├── design.md # 需求设计文档
95
+ │ ├── server.md # 服务端文档
96
+ │ └── client.md # 客户端文档
97
+ ├── init.sql # 初始化 SQL
98
+ ├── rest.test.ts # 集成测试
99
+ ├── .env / .env.test # 环境配置
100
+ └── package.json
101
+ ```
102
+
103
+ ## License
104
+
105
+ MIT
package/auth.ts ADDED
@@ -0,0 +1,164 @@
1
+ /**
2
+ * auth.ts — 鉴权中间件(JWT + Basic Auth)、登录 / 注册 / 用户资料
3
+ *
4
+ * 公开路径(无需鉴权):
5
+ * POST /api/auth/login
6
+ * POST /api/auth/register
7
+ * GET /api/health
8
+ *
9
+ * JWT payload: { sub: username, uid: userId, iat, exp }
10
+ * Basic Auth: Authorization: Basic base64(username:password)
11
+ */
12
+ import type {Context, Hono, Next} from "hono";
13
+ import {sign as jwtSign, verify as jwtVerify} from "hono/jwt";
14
+ import {zValidator} from "@hono/zod-validator";
15
+ import {type AppEnv, AppError, authBodySchema, cfg, ok, q, zodHook,} from "./types.ts";
16
+ import {getTable, run} from "./db.ts";
17
+
18
+ /* ═══════════ 公开路径集合 ═══════════ */
19
+
20
+ const PUBLIC = new Set(["/api/auth/login", "/api/auth/register", "/api/health"]);
21
+
22
+ /* ═══════════ 鉴权中间件 ═══════════ */
23
+
24
+ export const authMiddleware = async (
25
+ c: Context<AppEnv>,
26
+ next: Next,
27
+ ) => {
28
+ if (PUBLIC.has(c.req.path)) return next();
29
+
30
+ const header = c.req.header("Authorization") ?? "";
31
+
32
+ /* ── JWT: Bearer <token> ── */
33
+ if (header.startsWith("Bearer ")) {
34
+ try {
35
+ const payload = await jwtVerify(header.slice(7), cfg.jwtSecret, "HS256");
36
+ c.set("userId", payload.uid as number);
37
+ c.set("username", payload.sub as string);
38
+ return next();
39
+ } catch {
40
+ /* JWT 失败 → 尝试 Basic Auth */
41
+ }
42
+ }
43
+
44
+ /* ── Basic Auth: Basic base64(user:pass) ── */
45
+ if (cfg.basicAuth && header.startsWith("Basic ")) {
46
+ try {
47
+ const decoded = atob(header.slice(6));
48
+ const sep = decoded.indexOf(":");
49
+ if (sep > 0) {
50
+ const username = decoded.slice(0, sep);
51
+ const password = decoded.slice(sep + 1);
52
+ const user = await findUser(username);
53
+ if (user && user.password === password) {
54
+ c.set("userId", user.id as number);
55
+ c.set("username", username);
56
+ return next();
57
+ }
58
+ }
59
+ } catch {
60
+ /* ignore decode errors */
61
+ }
62
+ }
63
+
64
+ throw new AppError("AUTH_ERROR", "Unauthorized");
65
+ };
66
+
67
+ /* ═══════════ 内部工具 ═══════════ */
68
+
69
+ async function findUser(username: string) {
70
+ const rows = await run(
71
+ `SELECT id, username, password
72
+ FROM ${q(cfg.authTable)}
73
+ WHERE username = $1`,
74
+ [username],
75
+ );
76
+ return rows.length > 0 ? (rows[0] as any) : null;
77
+ }
78
+
79
+ async function issueToken(uid: number, username: string): Promise<string> {
80
+ const now = Math.floor(Date.now() / 1000);
81
+ return jwtSign(
82
+ {sub: username, uid, iat: now, exp: now + cfg.jwtExp},
83
+ cfg.jwtSecret,
84
+ );
85
+ }
86
+
87
+ /* ═══════════ 注册路由 ═══════════ */
88
+
89
+ export function registerAuthRoutes(app: Hono<AppEnv>) {
90
+ /* ── POST /api/auth/login ── */
91
+ app.post(
92
+ "/api/auth/login",
93
+ zValidator("json", authBodySchema, zodHook as any),
94
+ async (c) => {
95
+ const {username, password} = c.req.valid("json");
96
+ const user = await findUser(username);
97
+ if (!user || user.password !== password)
98
+ throw new AppError("AUTH_ERROR", "Invalid username or password");
99
+ return c.json(ok(await issueToken(user.id, username)));
100
+ },
101
+ );
102
+
103
+ /* ── POST /api/auth/register ── */
104
+ app.post(
105
+ "/api/auth/register",
106
+ zValidator("json", authBodySchema, zodHook as any),
107
+ async (c) => {
108
+ const {username, password} = c.req.valid("json");
109
+ if (await findUser(username))
110
+ throw new AppError("AUTH_ERROR", `User "${username}" already exists`);
111
+ await run(
112
+ `INSERT INTO ${q(cfg.authTable)} (username, password)
113
+ VALUES ($1, $2)`,
114
+ [username, password],
115
+ );
116
+ const user = await findUser(username);
117
+ return c.json(ok(await issueToken(user.id, username)));
118
+ },
119
+ );
120
+
121
+ /* ── GET /api/auth/profile — 获取当前用户资料(去掉 id 和 password) ── */
122
+ app.get("/api/auth/profile", async (c) => {
123
+ const userId = c.get("userId");
124
+ const rows = await run(
125
+ `SELECT *
126
+ FROM ${q(cfg.authTable)}
127
+ WHERE id = $1`,
128
+ [userId],
129
+ );
130
+ if (rows.length === 0) throw new AppError("AUTH_ERROR", "User not found");
131
+ const data = {...(rows[0] as any)};
132
+ delete data.id;
133
+ delete data.password;
134
+ return c.json(ok(data));
135
+ });
136
+
137
+ /* ── POST /api/auth/profile — 增量更新当前用户资料 ── */
138
+ app.post("/api/auth/profile", async (c) => {
139
+ const userId = c.get("userId");
140
+ const body = (await c.req.json()) as Record<string, unknown>;
141
+ const meta = getTable(cfg.authTable)!;
142
+
143
+ const sets: string[] = [];
144
+ const vals: unknown[] = [];
145
+ let n = 1;
146
+ for (const [k, v] of Object.entries(body)) {
147
+ /* 只更新表中实际存在的列(id 不允许修改) */
148
+ if (meta.colMap.has(k) && k !== "id") {
149
+ sets.push(`${q(k)} = $${n++}`);
150
+ vals.push(v);
151
+ }
152
+ }
153
+ if (sets.length === 0) return c.json(ok(null));
154
+
155
+ vals.push(userId);
156
+ await run(
157
+ `UPDATE ${q(cfg.authTable)}
158
+ SET ${sets.join(", ")}
159
+ WHERE id = $${n}`,
160
+ vals,
161
+ );
162
+ return c.json(ok(null));
163
+ });
164
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ import "../server.ts";
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@dtdyq/restbase-client",
3
+ "version": "1.0.0",
4
+ "description": "Type-safe, zero-dependency client for RestBase API — works in Browser / Node / Bun / Deno",
5
+ "keywords": ["restbase", "rest", "api", "client", "typescript", "query-builder"],
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./restbase-client.ts",
9
+ "module": "./restbase-client.ts",
10
+ "types": "./restbase-client.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./restbase-client.ts",
14
+ "bun": "./restbase-client.ts",
15
+ "import": "./restbase-client.ts",
16
+ "default": "./restbase-client.ts"
17
+ }
18
+ },
19
+ "files": [
20
+ "restbase-client.ts"
21
+ ]
22
+ }