weifuwu 0.33.0 → 0.33.2
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 +303 -145
- package/dist/client/app.d.ts +21 -0
- package/dist/client/index.d.ts +21 -0
- package/dist/client/index.js +320 -0
- package/dist/client/jsx-runtime.d.ts +60 -0
- package/dist/client/jsx-runtime.js +320 -0
- package/dist/client/router.d.ts +51 -0
- package/dist/client/signal.d.ts +19 -0
- package/dist/client/types.d.ts +45 -0
- package/dist/index.d.ts +0 -7
- package/dist/index.js +13 -925
- package/dist/types.d.ts +0 -1
- package/package.json +9 -42
- package/dist/middleware/esbuild-dev.d.ts +0 -69
- package/dist/middleware/esbuild-dev.js +0 -335
- package/dist/middleware/tailwind-dev.d.ts +0 -43
- package/dist/middleware/tailwind-dev.js +0 -199
- package/dist/react/client.d.ts +0 -76
- package/dist/react/client.js +0 -182
- package/dist/react/compile.d.ts +0 -17
- package/dist/react/context.d.ts +0 -2
- package/dist/react/error-boundary.d.ts +0 -30
- package/dist/react/hooks.d.ts +0 -6
- package/dist/react/index.d.ts +0 -92
- package/dist/react/index.js +0 -910
- package/dist/react/types.d.ts +0 -146
package/README.md
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
# weifuwu
|
|
2
2
|
|
|
3
|
-
**AI SaaS
|
|
3
|
+
**AI SaaS full-stack framework** — `(req, ctx) => Response` + `(props, ctx) => JSX`
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install weifuwu
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
One package. Backend + frontend. User system, instant messaging, RAG knowledge base, AI Agent, CMS, dynamic data storage, and a reactive frontend framework. Configure environment variables and go.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
## Quick Start
|
|
14
14
|
|
|
15
|
+
### Backend
|
|
16
|
+
|
|
15
17
|
```ts
|
|
16
18
|
import { serve, Router, postgres, user, kb, agent, messager } from 'weifuwu'
|
|
17
19
|
import { openai } from '@ai-sdk/openai'
|
|
@@ -34,7 +36,47 @@ app.post('/api/chat', async (req, ctx) => {
|
|
|
34
36
|
serve(app, { port: 3000 })
|
|
35
37
|
```
|
|
36
38
|
|
|
37
|
-
###
|
|
39
|
+
### Frontend
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { signal, Show, For, createApp, router, RouteView } from 'weifuwu/client'
|
|
43
|
+
import type { WfuiContext } from 'weifuwu/client'
|
|
44
|
+
|
|
45
|
+
function HomePage(_props: {}, ctx: WfuiContext) {
|
|
46
|
+
return (
|
|
47
|
+
<div>
|
|
48
|
+
<h1>Hello weifuwu</h1>
|
|
49
|
+
<p>当前路径: {ctx.route.path}</p>
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const app = createApp()
|
|
55
|
+
app.use(router({
|
|
56
|
+
routes: [
|
|
57
|
+
{ path: '/', component: HomePage, title: '首页' },
|
|
58
|
+
{ path: '/chat/:id', component: ChatPage, title: '聊天' },
|
|
59
|
+
],
|
|
60
|
+
}))
|
|
61
|
+
app.mount('#root', AppShell)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
// tsconfig.json
|
|
66
|
+
{ "jsx": "react-jsx", "jsxImportSource": "weifuwu/client" }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
// build.mjs
|
|
71
|
+
esbuild.build({
|
|
72
|
+
entryPoints: ['src/main.tsx'],
|
|
73
|
+
jsx: 'automatic',
|
|
74
|
+
jsxImportSource: 'weifuwu/client',
|
|
75
|
+
bundle: true,
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Environment Variables
|
|
38
80
|
|
|
39
81
|
| Variable | Default | Used by |
|
|
40
82
|
|----------|---------|---------|
|
|
@@ -49,14 +91,16 @@ serve(app, { port: 3000 })
|
|
|
49
91
|
|
|
50
92
|
## Modules
|
|
51
93
|
|
|
94
|
+
### Backend
|
|
95
|
+
|
|
52
96
|
| Module | Import | Dependency | Purpose |
|
|
53
97
|
|--------|--------|-----------|---------|
|
|
54
|
-
| User | `user()` | `postgres()` |
|
|
55
|
-
| Messager | `messager()` | `postgres()`, `user()` |
|
|
56
|
-
| KB | `kb()` | `postgres()` | RAG
|
|
57
|
-
| Agent | `agent()` | — | LLM
|
|
58
|
-
| CMS | `cms()` | `postgres()`, `user()` |
|
|
59
|
-
| Base | `base()` | `postgres()`, `user()` |
|
|
98
|
+
| User | `user()` | `postgres()` | Auth, JWT, roles |
|
|
99
|
+
| Messager | `messager()` | `postgres()`, `user()` | IM + AI conversation layer |
|
|
100
|
+
| KB | `kb()` | `postgres()` | RAG knowledge base |
|
|
101
|
+
| Agent | `agent()` | — | LLM chat, tool calling, streaming |
|
|
102
|
+
| CMS | `cms()` | `postgres()`, `user()` | Blog, docs, changelog |
|
|
103
|
+
| Base | `base()` | `postgres()`, `user()` | Dynamic data engine |
|
|
60
104
|
|
|
61
105
|
### Middleware
|
|
62
106
|
|
|
@@ -84,6 +128,20 @@ serve(app, { port: 3000 })
|
|
|
84
128
|
| `queue()` | Job queue + cron |
|
|
85
129
|
| `createHub()` | WebSocket pub/sub |
|
|
86
130
|
|
|
131
|
+
### Frontend (weifuwu/client)
|
|
132
|
+
|
|
133
|
+
| Import | Purpose |
|
|
134
|
+
|--------|---------|
|
|
135
|
+
| `signal()` | Reactive state |
|
|
136
|
+
| `computed()` | Derived signals |
|
|
137
|
+
| `effect()` | Auto-tracked side effects |
|
|
138
|
+
| `<Show>` | Conditional rendering |
|
|
139
|
+
| `<For>` | List rendering |
|
|
140
|
+
| `<RouteView>` | Route outlet |
|
|
141
|
+
| `createApp()` | App instance with middleware chain |
|
|
142
|
+
| `router()` | Hash/history router with params + query |
|
|
143
|
+
| `domMount()` | Direct DOM mounting |
|
|
144
|
+
|
|
87
145
|
### Utilities
|
|
88
146
|
|
|
89
147
|
| Import | Purpose |
|
|
@@ -92,9 +150,95 @@ serve(app, { port: 3000 })
|
|
|
92
150
|
|
|
93
151
|
---
|
|
94
152
|
|
|
153
|
+
## Frontend (weifuwu/client)
|
|
154
|
+
|
|
155
|
+
**weifuwu/client** is a reactive frontend framework built on Signal + TSX. Zero virtual DOM, zero dependencies, ~600 lines total.
|
|
156
|
+
|
|
157
|
+
### Concepts
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
// 1. Signal — reactive data
|
|
161
|
+
const count = signal(0)
|
|
162
|
+
count.value = count.value + 1 // DOM updates automatically
|
|
163
|
+
|
|
164
|
+
// 2. Computed — derived signals
|
|
165
|
+
const doubled = computed(() => count.value * 2)
|
|
166
|
+
|
|
167
|
+
// 3. Effect — auto-tracked side effects
|
|
168
|
+
effect(() => console.log('count:', count.value))
|
|
169
|
+
|
|
170
|
+
// 4. Component — (props, ctx) => JSX
|
|
171
|
+
function MyComponent({ name }: { name: string }, ctx: WfuiContext) {
|
|
172
|
+
return <div>Hello {name}</div>
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### createApp + Middleware
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { createApp, router, RouteView } from 'weifuwu/client'
|
|
180
|
+
import type { WfuiContext, RouteDef } from 'weifuwu/client'
|
|
181
|
+
|
|
182
|
+
const app = createApp()
|
|
183
|
+
app.use(router({ routes, mode: 'hash' }))
|
|
184
|
+
app.mount('#root', AppShell)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Router
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
const routes: RouteDef[] = [
|
|
191
|
+
{ path: '/', component: HomePage, title: '首页' },
|
|
192
|
+
{ path: '/chat/:id', component: ChatPage, title: '聊天' },
|
|
193
|
+
{ path: '/user/:name', component: UserPage, title: '用户' },
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
app.use(router({
|
|
197
|
+
routes,
|
|
198
|
+
notFound: NotFound,
|
|
199
|
+
mode: 'hash', // or 'history'
|
|
200
|
+
}))
|
|
201
|
+
|
|
202
|
+
// In layout:
|
|
203
|
+
function AppShell(_, ctx) {
|
|
204
|
+
return (
|
|
205
|
+
<div>
|
|
206
|
+
<nav>
|
|
207
|
+
<a onClick={() => ctx.app.navigate('/')}>首页</a>
|
|
208
|
+
<a onClick={() => ctx.app.navigate('/chat/123')}>聊天</a>
|
|
209
|
+
</nav>
|
|
210
|
+
<main>
|
|
211
|
+
<RouteView /> {/* ← renders matched route */}
|
|
212
|
+
</main>
|
|
213
|
+
</div>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Route params and query:
|
|
218
|
+
ctx.route.path // "/chat/123"
|
|
219
|
+
ctx.route.params // { id: "123" }
|
|
220
|
+
ctx.route.query // { tab: "settings" }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Show / For
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
// Conditional rendering (supports Signal)
|
|
227
|
+
<Show when={isLoggedIn} fallback={<LoginPage />}>
|
|
228
|
+
<Dashboard />
|
|
229
|
+
</Show>
|
|
230
|
+
|
|
231
|
+
// List rendering (supports Signal)
|
|
232
|
+
<For each={filteredItems}>
|
|
233
|
+
{(item) => <div>{item.name}</div>}
|
|
234
|
+
</For>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
95
239
|
## user
|
|
96
240
|
|
|
97
|
-
|
|
241
|
+
Auth, registration, JWT, password management, roles.
|
|
98
242
|
|
|
99
243
|
```ts
|
|
100
244
|
import { user, requireRole } from 'weifuwu'
|
|
@@ -102,24 +246,24 @@ import { user, requireRole } from 'weifuwu'
|
|
|
102
246
|
app.use(postgres())
|
|
103
247
|
app.use(user({ secret: process.env.JWT_SECRET }))
|
|
104
248
|
|
|
105
|
-
//
|
|
249
|
+
// Register
|
|
106
250
|
app.post('/api/register', async (req, ctx) => {
|
|
107
251
|
const result = await ctx.userModule.register(await req.json())
|
|
108
252
|
return Response.json(result)
|
|
109
253
|
})
|
|
110
|
-
//
|
|
254
|
+
// Login
|
|
111
255
|
app.post('/api/login', async (req, ctx) => {
|
|
112
256
|
const { email, password } = await req.json()
|
|
113
257
|
const result = await ctx.userModule.login(email, password)
|
|
114
258
|
if (!result) return new Response('Unauthorized', { status: 401 })
|
|
115
259
|
return Response.json(result)
|
|
116
260
|
})
|
|
117
|
-
//
|
|
261
|
+
// Current user
|
|
118
262
|
app.get('/api/me', async (req, ctx) => {
|
|
119
263
|
if (!ctx.user) return new Response('Unauthorized', { status: 401 })
|
|
120
264
|
return Response.json(ctx.user)
|
|
121
265
|
})
|
|
122
|
-
//
|
|
266
|
+
// Admin only
|
|
123
267
|
app.get('/api/admin/users', requireRole('admin'), async (req, ctx) => {
|
|
124
268
|
return Response.json(await ctx.userModule.listUsers())
|
|
125
269
|
})
|
|
@@ -129,28 +273,28 @@ app.get('/api/admin/users', requireRole('admin'), async (req, ctx) => {
|
|
|
129
273
|
|
|
130
274
|
| Method | Returns | Description |
|
|
131
275
|
|--------|---------|-------------|
|
|
132
|
-
| `register(input)` | `{ user, token }` |
|
|
133
|
-
| `login(email, pw)` | `{ user, token } \| null` |
|
|
134
|
-
| `getUserById(id)` | `UserRecord \| null` |
|
|
135
|
-
| `getUserByEmail(email)` | `UserRecord \| null` |
|
|
136
|
-
| `updateUser(id, input)` | `UserRecord \| null` |
|
|
137
|
-
| `changePassword(id, oldPw, newPw)` | `boolean` |
|
|
138
|
-
| `deleteUser(id)` | `boolean` |
|
|
139
|
-
| `listUsers(inactive?)` | `UserRecord[]` |
|
|
140
|
-
| `generateToken(user)` | `string` |
|
|
141
|
-
| `verifyToken(token)` | `TokenPayload \| null` |
|
|
142
|
-
| `refreshToken(token)` | `string \| null` |
|
|
276
|
+
| `register(input)` | `{ user, token }` | Register |
|
|
277
|
+
| `login(email, pw)` | `{ user, token } \| null` | Login |
|
|
278
|
+
| `getUserById(id)` | `UserRecord \| null` | Get by ID |
|
|
279
|
+
| `getUserByEmail(email)` | `UserRecord \| null` | Get by email |
|
|
280
|
+
| `updateUser(id, input)` | `UserRecord \| null` | Update |
|
|
281
|
+
| `changePassword(id, oldPw, newPw)` | `boolean` | Change password |
|
|
282
|
+
| `deleteUser(id)` | `boolean` | Soft delete |
|
|
283
|
+
| `listUsers(inactive?)` | `UserRecord[]` | List users |
|
|
284
|
+
| `generateToken(user)` | `string` | Issue JWT |
|
|
285
|
+
| `verifyToken(token)` | `TokenPayload \| null` | Verify JWT |
|
|
286
|
+
| `refreshToken(token)` | `string \| null` | Refresh JWT |
|
|
143
287
|
|
|
144
288
|
### ctx.user
|
|
145
289
|
|
|
146
|
-
|
|
290
|
+
Auto-resolved from `Authorization: Bearer` or `token` cookie.
|
|
147
291
|
|
|
148
292
|
```ts
|
|
149
293
|
interface User {
|
|
150
294
|
id: string
|
|
151
295
|
name: string
|
|
152
296
|
email: string
|
|
153
|
-
role: string
|
|
297
|
+
role: string
|
|
154
298
|
[key: string]: unknown
|
|
155
299
|
}
|
|
156
300
|
```
|
|
@@ -159,19 +303,19 @@ interface User {
|
|
|
159
303
|
|
|
160
304
|
```ts
|
|
161
305
|
app.get('/admin', requireRole('admin'), handler)
|
|
162
|
-
//
|
|
306
|
+
// No auth → 401, wrong role → 403
|
|
163
307
|
```
|
|
164
308
|
|
|
165
|
-
###
|
|
309
|
+
### Security
|
|
166
310
|
|
|
167
|
-
-
|
|
168
|
-
-
|
|
311
|
+
- Password: scrypt + 32-byte random salt
|
|
312
|
+
- Token: HMAC SHA-256, 7 day expiry
|
|
169
313
|
|
|
170
314
|
---
|
|
171
315
|
|
|
172
316
|
## messager
|
|
173
317
|
|
|
174
|
-
|
|
318
|
+
Instant messaging + AI conversation layer. Direct/group chat, message persistence, WebSocket push.
|
|
175
319
|
|
|
176
320
|
```ts
|
|
177
321
|
import { messager } from 'weifuwu'
|
|
@@ -180,7 +324,7 @@ app.use(postgres())
|
|
|
180
324
|
app.use(user())
|
|
181
325
|
app.use(messager())
|
|
182
326
|
|
|
183
|
-
// WebSocket —
|
|
327
|
+
// WebSocket — auto-join all user conversations
|
|
184
328
|
app.ws('/ws', {
|
|
185
329
|
async open(ws, ctx) {
|
|
186
330
|
for (const c of await ctx.messager.getConversations()) {
|
|
@@ -209,32 +353,32 @@ app.get('/api/conversations/:id/messages', async (req, ctx) => {
|
|
|
209
353
|
|
|
210
354
|
| Method | Returns | Description |
|
|
211
355
|
|--------|---------|-------------|
|
|
212
|
-
| `createDirectConversation(userId)` | `Conversation` |
|
|
213
|
-
| `createGroupConversation(title, userIds)` | `Conversation` |
|
|
214
|
-
| `sendMessage(convId, body)` | `Message` |
|
|
215
|
-
| `getMessages(convId, opts?)` | `Message[]` |
|
|
216
|
-
| `editMessage(msgId, body)` | `Message \| null` |
|
|
217
|
-
| `deleteMessage(msgId)` | `boolean` |
|
|
218
|
-
| `getConversations()` | `Conversation[]` |
|
|
219
|
-
| `getConversation(id)` | `Conversation \| null` |
|
|
220
|
-
| `markRead(convId)` | `void` |
|
|
221
|
-
| `getUnreadCount()` | `{ total, byConversation }` |
|
|
222
|
-
| `addParticipants(convId, userIds)` | `void` |
|
|
223
|
-
| `removeParticipant(convId, userId?)` | `boolean` |
|
|
356
|
+
| `createDirectConversation(userId)` | `Conversation` | Create/reuse DM |
|
|
357
|
+
| `createGroupConversation(title, userIds)` | `Conversation` | Create group |
|
|
358
|
+
| `sendMessage(convId, body)` | `Message` | Send, auto-broadcast to `conversation:{id}` room |
|
|
359
|
+
| `getMessages(convId, opts?)` | `Message[]` | Cursor pagination |
|
|
360
|
+
| `editMessage(msgId, body)` | `Message \| null` | Edit (24h window) |
|
|
361
|
+
| `deleteMessage(msgId)` | `boolean` | Soft delete |
|
|
362
|
+
| `getConversations()` | `Conversation[]` | List with unread + last message |
|
|
363
|
+
| `getConversation(id)` | `Conversation \| null` | Get detail |
|
|
364
|
+
| `markRead(convId)` | `void` | Mark as read |
|
|
365
|
+
| `getUnreadCount()` | `{ total, byConversation }` | Unread stats |
|
|
366
|
+
| `addParticipants(convId, userIds)` | `void` | Add members |
|
|
367
|
+
| `removeParticipant(convId, userId?)` | `boolean` | Leave / kick |
|
|
224
368
|
|
|
225
|
-
###
|
|
369
|
+
### Storage
|
|
226
370
|
|
|
227
|
-
3
|
|
371
|
+
3 tables: `conversations` / `participants` / `messages`. Auto-migration.
|
|
228
372
|
|
|
229
|
-
### AI
|
|
373
|
+
### AI Conversations
|
|
230
374
|
|
|
231
|
-
messager + agent
|
|
375
|
+
messager + agent = ChatGPT foundation. Messager handles sessions + push, agent handles LLM generation.
|
|
232
376
|
|
|
233
377
|
---
|
|
234
378
|
|
|
235
379
|
## kb
|
|
236
380
|
|
|
237
|
-
RAG
|
|
381
|
+
RAG knowledge base. Import docs → auto-chunk → DashScope embedding → pgvector storage → semantic search.
|
|
238
382
|
|
|
239
383
|
```ts
|
|
240
384
|
import { kb } from 'weifuwu'
|
|
@@ -242,14 +386,14 @@ import { kb } from 'weifuwu'
|
|
|
242
386
|
app.use(postgres())
|
|
243
387
|
app.use(kb())
|
|
244
388
|
|
|
245
|
-
//
|
|
389
|
+
// Import
|
|
246
390
|
app.post('/api/kb/import', async (req, ctx) => {
|
|
247
391
|
const { title, content } = await req.json()
|
|
248
392
|
const result = await ctx.kb.importText(title, content)
|
|
249
393
|
return Response.json(result, { status: 201 })
|
|
250
394
|
})
|
|
251
395
|
|
|
252
|
-
//
|
|
396
|
+
// Search
|
|
253
397
|
app.post('/api/kb/search', async (req, ctx) => {
|
|
254
398
|
const { query } = await req.json()
|
|
255
399
|
return Response.json(await ctx.kb.search(query, { limit: 5 }))
|
|
@@ -260,30 +404,30 @@ app.post('/api/kb/search', async (req, ctx) => {
|
|
|
260
404
|
|
|
261
405
|
| Method | Returns | Description |
|
|
262
406
|
|--------|---------|-------------|
|
|
263
|
-
| `importText(title, text, opts?)` | `{ document, chunks }` |
|
|
264
|
-
| `importDocuments(docs)` | `Document[]` |
|
|
265
|
-
| `search(query, opts?)` | `SearchResult[]` |
|
|
266
|
-
| `list()` | `Document[]` |
|
|
267
|
-
| `get(id)` | `Document \| null` |
|
|
268
|
-
| `getChunks(documentId)` | `Chunk[]` |
|
|
269
|
-
| `delete(id)` | `boolean` |
|
|
407
|
+
| `importText(title, text, opts?)` | `{ document, chunks }` | Import → chunk → embed → store |
|
|
408
|
+
| `importDocuments(docs)` | `Document[]` | Batch import |
|
|
409
|
+
| `search(query, opts?)` | `SearchResult[]` | Semantic search (cosine) |
|
|
410
|
+
| `list()` | `Document[]` | List documents |
|
|
411
|
+
| `get(id)` | `Document \| null` | Get document |
|
|
412
|
+
| `getChunks(documentId)` | `Chunk[]` | Get chunks |
|
|
413
|
+
| `delete(id)` | `boolean` | Delete + cascade chunks |
|
|
270
414
|
|
|
271
|
-
###
|
|
415
|
+
### Configuration
|
|
272
416
|
|
|
273
417
|
```ts
|
|
274
|
-
//
|
|
418
|
+
// Default: DashScope text-embedding-v4 (env: DASHSCOPE_API_KEY)
|
|
275
419
|
app.use(kb())
|
|
276
420
|
|
|
277
|
-
//
|
|
421
|
+
// Custom embedding
|
|
278
422
|
app.use(kb({
|
|
279
|
-
embed: async (text) => { /*
|
|
423
|
+
embed: async (text) => { /* return number[] */ },
|
|
280
424
|
dimensions: 1536,
|
|
281
|
-
chunkSize: 512, //
|
|
282
|
-
chunkOverlap: 64,
|
|
425
|
+
chunkSize: 512, // tokens
|
|
426
|
+
chunkOverlap: 64,
|
|
283
427
|
}))
|
|
284
428
|
```
|
|
285
429
|
|
|
286
|
-
###
|
|
430
|
+
### Integration with Agent
|
|
287
431
|
|
|
288
432
|
```ts
|
|
289
433
|
app.use(agent({
|
|
@@ -294,16 +438,16 @@ app.use(agent({
|
|
|
294
438
|
}))
|
|
295
439
|
```
|
|
296
440
|
|
|
297
|
-
###
|
|
441
|
+
### Storage
|
|
298
442
|
|
|
299
|
-
- `kb_documents` —
|
|
300
|
-
- `kb_chunks` —
|
|
443
|
+
- `kb_documents` — document metadata
|
|
444
|
+
- `kb_chunks` — chunk content + VECTOR(1536) + TSVECTOR GIN index
|
|
301
445
|
|
|
302
446
|
---
|
|
303
447
|
|
|
304
448
|
## agent
|
|
305
449
|
|
|
306
|
-
AI Agent — LLM
|
|
450
|
+
AI Agent — LLM chat, tool calling, RAG, streaming.
|
|
307
451
|
|
|
308
452
|
```ts
|
|
309
453
|
import { agent } from 'weifuwu'
|
|
@@ -327,13 +471,13 @@ app.use(agent({
|
|
|
327
471
|
maxSteps: 5,
|
|
328
472
|
}))
|
|
329
473
|
|
|
330
|
-
//
|
|
474
|
+
// Streaming
|
|
331
475
|
app.post('/api/chat', async (req, ctx) => {
|
|
332
476
|
const { messages } = await req.json()
|
|
333
477
|
return ctx.agent.chatStreamResponse({ messages })
|
|
334
478
|
})
|
|
335
479
|
|
|
336
|
-
//
|
|
480
|
+
// Non-streaming
|
|
337
481
|
app.post('/api/chat/sync', async (req, ctx) => {
|
|
338
482
|
const { prompt } = await req.json()
|
|
339
483
|
const text = await ctx.agent.chat(prompt)
|
|
@@ -344,31 +488,31 @@ app.post('/api/chat/sync', async (req, ctx) => {
|
|
|
344
488
|
### ctx.agent API
|
|
345
489
|
|
|
346
490
|
| Method | Description |
|
|
347
|
-
|
|
348
|
-
| `chat(prompt, opts?)` |
|
|
349
|
-
| `chatStreamResponse({ messages })` | SSE
|
|
491
|
+
|-------------|-------------|
|
|
492
|
+
| `chat(prompt, opts?)` | Non-streaming, returns text |
|
|
493
|
+
| `chatStreamResponse({ messages })` | SSE stream (compatible with `useChat`) |
|
|
350
494
|
|
|
351
|
-
###
|
|
495
|
+
### Default Model
|
|
352
496
|
|
|
353
|
-
- LLM: DeepSeek-V4-Flash
|
|
354
|
-
-
|
|
355
|
-
-
|
|
497
|
+
- LLM: DeepSeek-V4-Flash (via `@ai-sdk/openai` + `baseURL: 'https://api.deepseek.com/v1'`)
|
|
498
|
+
- Override with `DEEPSEEK_MODEL` env
|
|
499
|
+
- API key via `DEEPSEEK_API_KEY` or `OPENAI_API_KEY` env
|
|
356
500
|
|
|
357
501
|
### Features
|
|
358
502
|
|
|
359
503
|
| Feature | Description |
|
|
360
504
|
|---------|-------------|
|
|
361
|
-
| `knowledge.search` | RAG
|
|
362
|
-
| `tools` |
|
|
363
|
-
| `sandbox: true` |
|
|
364
|
-
| `store` |
|
|
365
|
-
| `agents` |
|
|
505
|
+
| `knowledge.search` | RAG callback, auto-injected into system prompt |
|
|
506
|
+
| `tools` | Tool definitions, auto-loop (maxSteps) |
|
|
507
|
+
| `sandbox: true` | Integrates with `ctx.sandbox` |
|
|
508
|
+
| `store` | Session persistence (save/load) |
|
|
509
|
+
| `agents` | Multi-agent orchestration |
|
|
366
510
|
|
|
367
511
|
---
|
|
368
512
|
|
|
369
513
|
## cms
|
|
370
514
|
|
|
371
|
-
|
|
515
|
+
Content management — blog, docs, changelog.
|
|
372
516
|
|
|
373
517
|
```ts
|
|
374
518
|
import { cms, requireRole } from 'weifuwu'
|
|
@@ -377,7 +521,7 @@ app.use(postgres())
|
|
|
377
521
|
app.use(user())
|
|
378
522
|
app.use(cms())
|
|
379
523
|
|
|
380
|
-
//
|
|
524
|
+
// Public
|
|
381
525
|
app.get('/api/posts', async (req, ctx) => {
|
|
382
526
|
return Response.json(await ctx.cms.list({ type: 'post', status: 'published' }))
|
|
383
527
|
})
|
|
@@ -387,7 +531,7 @@ app.get('/api/posts/:slug', async (req, ctx) => {
|
|
|
387
531
|
return Response.json(post)
|
|
388
532
|
})
|
|
389
533
|
|
|
390
|
-
//
|
|
534
|
+
// Admin
|
|
391
535
|
app.post('/api/admin/posts', requireRole('admin'), async (req, ctx) => {
|
|
392
536
|
const post = await ctx.cms.create(await req.json())
|
|
393
537
|
return Response.json(post, { status: 201 })
|
|
@@ -403,31 +547,31 @@ app.patch('/api/admin/posts/:id', requireRole('admin'), async (req, ctx) => {
|
|
|
403
547
|
|
|
404
548
|
| Method | Returns | Description |
|
|
405
549
|
|--------|---------|-------------|
|
|
406
|
-
| `create(input)` | `Content` |
|
|
407
|
-
| `get(slug)` | `Content \| null` |
|
|
408
|
-
| `getById(id)` | `Content \| null` |
|
|
409
|
-
| `update(id, input)` | `Content \| null` |
|
|
410
|
-
| `delete(id)` | `boolean` |
|
|
411
|
-
| `list(opts?)` | `Content[]` |
|
|
412
|
-
| `publish(id)` | `Content \| null` |
|
|
413
|
-
| `unpublish(id)` | `Content \| null` |
|
|
414
|
-
| `listTags()` | `TagWithCount[]` |
|
|
415
|
-
| `createTag(name)` | `Tag` |
|
|
550
|
+
| `create(input)` | `Content` | Create (admin) |
|
|
551
|
+
| `get(slug)` | `Content \| null` | Get by slug |
|
|
552
|
+
| `getById(id)` | `Content \| null` | Get by ID |
|
|
553
|
+
| `update(id, input)` | `Content \| null` | Update (admin) |
|
|
554
|
+
| `delete(id)` | `boolean` | Delete (admin) |
|
|
555
|
+
| `list(opts?)` | `Content[]` | List with cursor |
|
|
556
|
+
| `publish(id)` | `Content \| null` | Publish (admin) |
|
|
557
|
+
| `unpublish(id)` | `Content \| null` | Unpublish (admin) |
|
|
558
|
+
| `listTags()` | `TagWithCount[]` | List tags |
|
|
559
|
+
| `createTag(name)` | `Tag` | Create tag |
|
|
416
560
|
|
|
417
561
|
### Features
|
|
418
562
|
|
|
419
|
-
-
|
|
420
|
-
-
|
|
421
|
-
- Slug
|
|
422
|
-
-
|
|
423
|
-
-
|
|
424
|
-
-
|
|
563
|
+
- Types: post / page / doc / changelog (any string)
|
|
564
|
+
- Status: draft / published / archived
|
|
565
|
+
- Slug: auto-generated, unique per type
|
|
566
|
+
- Tags: many-to-many, auto-created
|
|
567
|
+
- Tree: parent_id for hierarchy
|
|
568
|
+
- Auth: non-admin users see published only
|
|
425
569
|
|
|
426
570
|
---
|
|
427
571
|
|
|
428
572
|
## base
|
|
429
573
|
|
|
430
|
-
|
|
574
|
+
Dynamic data storage engine — let users define their own data structures (like Airtable).
|
|
431
575
|
|
|
432
576
|
```ts
|
|
433
577
|
import { base } from 'weifuwu'
|
|
@@ -436,13 +580,13 @@ app.use(postgres())
|
|
|
436
580
|
app.use(user())
|
|
437
581
|
app.use(base())
|
|
438
582
|
|
|
439
|
-
//
|
|
583
|
+
// Define schema
|
|
440
584
|
app.post('/api/bases', async (req, ctx) => {
|
|
441
585
|
const b = await ctx.base.create(await req.json())
|
|
442
586
|
return Response.json(b, { status: 201 })
|
|
443
587
|
})
|
|
444
588
|
|
|
445
|
-
//
|
|
589
|
+
// CRUD
|
|
446
590
|
app.post('/api/bases/:id/:table', async (req, ctx) => {
|
|
447
591
|
const row = await ctx.base.insert(ctx.params.id, ctx.params.table, await req.json())
|
|
448
592
|
return Response.json(row, { status: 201 })
|
|
@@ -461,35 +605,37 @@ app.get('/api/bases/:id/:table', async (req, ctx) => {
|
|
|
461
605
|
|
|
462
606
|
| Method | Returns | Description |
|
|
463
607
|
|--------|---------|-------------|
|
|
464
|
-
| `create({ name, tables })` | `BaseDef` |
|
|
465
|
-
| `defineTable(baseId, schema)` | `BaseDef` |
|
|
466
|
-
| `updateTable(baseId, name, schema)` | `BaseDef \| null` |
|
|
467
|
-
| `removeTable(baseId, name)` | `BaseDef \| null` |
|
|
468
|
-
| `insert(baseId, table, data)` | `Row` |
|
|
469
|
-
| `getRow(baseId, table, id)` | `Row \| null` |
|
|
470
|
-
| `updateRow(baseId, table, id, data)` | `Row \| null` |
|
|
471
|
-
| `deleteRow(baseId, table, id)` | `boolean` |
|
|
472
|
-
| `query(baseId, table, opts?)` | `Row[]` |
|
|
473
|
-
| `search(baseId, table, field, query)` | `Row[]` |
|
|
474
|
-
| `similaritySearch(baseId, table, field, vector)` | `Row[]` |
|
|
475
|
-
| `list()` / `get(id)` / `getBySlug(slug)` / `delete(id)` | — |
|
|
476
|
-
|
|
477
|
-
###
|
|
478
|
-
|
|
479
|
-
Fixed Slot
|
|
480
|
-
|
|
481
|
-
|
|
|
482
|
-
|
|
608
|
+
| `create({ name, tables })` | `BaseDef` | Create database |
|
|
609
|
+
| `defineTable(baseId, schema)` | `BaseDef` | Add table |
|
|
610
|
+
| `updateTable(baseId, name, schema)` | `BaseDef \| null` | Update table |
|
|
611
|
+
| `removeTable(baseId, name)` | `BaseDef \| null` | Remove table |
|
|
612
|
+
| `insert(baseId, table, data)` | `Row` | Insert row |
|
|
613
|
+
| `getRow(baseId, table, id)` | `Row \| null` | Get row |
|
|
614
|
+
| `updateRow(baseId, table, id, data)` | `Row \| null` | Update row |
|
|
615
|
+
| `deleteRow(baseId, table, id)` | `boolean` | Delete row |
|
|
616
|
+
| `query(baseId, table, opts?)` | `Row[]` | Query (filter/sort/limit/offset) |
|
|
617
|
+
| `search(baseId, table, field, query)` | `Row[]` | Full-text search |
|
|
618
|
+
| `similaritySearch(baseId, table, field, vector)` | `Row[]` | Vector search |
|
|
619
|
+
| `list()` / `get(id)` / `getBySlug(slug)` / `delete(id)` | — | Manage databases |
|
|
620
|
+
|
|
621
|
+
### Architecture
|
|
622
|
+
|
|
623
|
+
Fixed Slot: a single `base_data` table with ~120 physical columns:
|
|
624
|
+
|
|
625
|
+
| Type | Count | PG Type |
|
|
626
|
+
|------|:-----:|:--------:|
|
|
483
627
|
| text001..064 | 64 | TEXT |
|
|
484
628
|
| number001..032 | 32 | DOUBLE PRECISION |
|
|
485
629
|
| date001..008 | 8 | TIMESTAMPTZ |
|
|
486
630
|
| vector001..004 | 4 | VECTOR(1536) |
|
|
487
631
|
| search001..004 | 4 | TEXT |
|
|
488
|
-
| ext | 1 | JSONB
|
|
632
|
+
| ext | 1 | JSONB (overflow) |
|
|
489
633
|
|
|
490
|
-
|
|
634
|
+
Field name → physical column mapping stored in `base_column_map`. Fields beyond the physical columns overflow to ext JSONB.
|
|
491
635
|
|
|
492
|
-
pgvector
|
|
636
|
+
pgvector auto-detected (included in docker image).
|
|
637
|
+
|
|
638
|
+
---
|
|
493
639
|
|
|
494
640
|
---
|
|
495
641
|
|
|
@@ -507,12 +653,12 @@ app.all(path, ...handlers)
|
|
|
507
653
|
app.ws(path, ...middlewares, handler)
|
|
508
654
|
// handler: { open?, message?, close?, error? }
|
|
509
655
|
|
|
510
|
-
//
|
|
656
|
+
// Middleware & mounting
|
|
511
657
|
app.use(middleware)
|
|
512
658
|
app.mount(prefix, router)
|
|
513
659
|
app.plugin(fn)
|
|
514
660
|
app.onError(handler)
|
|
515
|
-
app.routes() //
|
|
661
|
+
app.routes() // debug: list all routes
|
|
516
662
|
```
|
|
517
663
|
|
|
518
664
|
---
|
|
@@ -546,7 +692,7 @@ await sql.sql`SELECT * FROM users WHERE id = ${id}`
|
|
|
546
692
|
await sql.sql.begin(async (sql) => { /* transaction */ })
|
|
547
693
|
```
|
|
548
694
|
|
|
549
|
-
`DATABASE_URL`
|
|
695
|
+
Reads `DATABASE_URL` env. Supports migrations, transactions, connection pool stats.
|
|
550
696
|
|
|
551
697
|
---
|
|
552
698
|
|
|
@@ -560,7 +706,7 @@ app.use(r) // → ctx.redis
|
|
|
560
706
|
await r.redis.set('key', 'value')
|
|
561
707
|
```
|
|
562
708
|
|
|
563
|
-
`REDIS_URL`
|
|
709
|
+
Reads `REDIS_URL` env (default: `redis://localhost:6379`).
|
|
564
710
|
|
|
565
711
|
---
|
|
566
712
|
|
|
@@ -585,24 +731,36 @@ q.run()
|
|
|
585
731
|
|
|
586
732
|
```
|
|
587
733
|
src/
|
|
588
|
-
├── index.ts ←
|
|
589
|
-
├── types.ts ← Context, Handler, Middleware
|
|
734
|
+
├── index.ts ← Entry, exports all modules
|
|
735
|
+
├── types.ts ← Context, Handler, Middleware types
|
|
590
736
|
├── core/ ← serve, router, ws, trace, logger
|
|
591
737
|
├── middleware/ ← cors, helmet, compress, rate-limit, upload, static, sandbox
|
|
592
|
-
├── user/ ←
|
|
593
|
-
├── messager/ ←
|
|
594
|
-
├── kb/ ← RAG
|
|
595
|
-
├── ai/ ← AI Agent
|
|
596
|
-
├── cms/ ←
|
|
597
|
-
├── base/ ←
|
|
598
|
-
├── postgres/ ← PostgreSQL
|
|
599
|
-
├── redis/ ← Redis
|
|
600
|
-
├── queue/ ←
|
|
601
|
-
├── react/ ← React SSR
|
|
738
|
+
├── user/ ← User system (CRUD, JWT, requireRole)
|
|
739
|
+
├── messager/ ← IM + AI conversation layer
|
|
740
|
+
├── kb/ ← RAG knowledge base (chunking, embedding, vector search)
|
|
741
|
+
├── ai/ ← AI Agent (LLM, tools, RAG)
|
|
742
|
+
├── cms/ ← Content management (blog, docs, changelog)
|
|
743
|
+
├── base/ ← Dynamic data engine (Fixed Slot)
|
|
744
|
+
├── postgres/ ← PostgreSQL client
|
|
745
|
+
├── redis/ ← Redis client
|
|
746
|
+
├── queue/ ← Job queue + cron
|
|
602
747
|
├── graphql.ts ← GraphQL
|
|
603
748
|
├── hub.ts ← WebSocket hub
|
|
749
|
+
├── client/ ← Frontend framework (signal, JSX, router)
|
|
750
|
+
│ ├── index.ts
|
|
751
|
+
│ ├── signal.ts
|
|
752
|
+
│ ├── jsx-runtime.ts
|
|
753
|
+
│ ├── app.ts
|
|
754
|
+
│ ├── router.ts
|
|
755
|
+
│ └── types.ts
|
|
604
756
|
└── test/ ← 281 tests
|
|
605
757
|
|
|
758
|
+
apps/demo/ ← Full-stack demo
|
|
759
|
+
├── src/main.tsx
|
|
760
|
+
├── public/index.html
|
|
761
|
+
├── tsconfig.json
|
|
762
|
+
└── scripts/build.mjs
|
|
763
|
+
|
|
606
764
|
docker-compose.yml ← postgres (pgvector) + redis
|
|
607
765
|
```
|
|
608
766
|
|
|
@@ -611,7 +769,7 @@ docker-compose.yml ← postgres (pgvector) + redis
|
|
|
611
769
|
## Development
|
|
612
770
|
|
|
613
771
|
```bash
|
|
614
|
-
docker compose up -d #
|
|
772
|
+
docker compose up -d # Start postgres + redis
|
|
615
773
|
npm run build # esbuild → dist/
|
|
616
774
|
npm run typecheck # tsc --noEmit
|
|
617
775
|
npm test # 281 tests
|