weifuwu 0.33.0 → 0.33.1
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 +145 -144
- package/dist/index.d.ts +0 -3
- package/dist/index.js +22 -470
- package/dist/middleware/esbuild-dev.d.ts +1 -15
- package/dist/middleware/esbuild-dev.js +17 -86
- package/dist/types.d.ts +0 -1
- package/package.json +1 -21
- 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,12 +1,12 @@
|
|
|
1
1
|
# weifuwu
|
|
2
2
|
|
|
3
|
-
**AI SaaS
|
|
3
|
+
**AI SaaS framework** — `(req, ctx) => Response`
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install weifuwu
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
User system, instant messaging, RAG knowledge base, AI Agent, CMS, dynamic data storage. Configure environment variables and go.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -34,7 +34,7 @@ app.post('/api/chat', async (req, ctx) => {
|
|
|
34
34
|
serve(app, { port: 3000 })
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
###
|
|
37
|
+
### Environment Variables
|
|
38
38
|
|
|
39
39
|
| Variable | Default | Used by |
|
|
40
40
|
|----------|---------|---------|
|
|
@@ -51,12 +51,12 @@ serve(app, { port: 3000 })
|
|
|
51
51
|
|
|
52
52
|
| Module | Import | Dependency | Purpose |
|
|
53
53
|
|--------|--------|-----------|---------|
|
|
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()` |
|
|
54
|
+
| User | `user()` | `postgres()` | Auth, JWT, roles |
|
|
55
|
+
| Messager | `messager()` | `postgres()`, `user()` | IM + AI conversation layer |
|
|
56
|
+
| KB | `kb()` | `postgres()` | RAG knowledge base |
|
|
57
|
+
| Agent | `agent()` | — | LLM chat, tool calling, streaming |
|
|
58
|
+
| CMS | `cms()` | `postgres()`, `user()` | Blog, docs, changelog |
|
|
59
|
+
| Base | `base()` | `postgres()`, `user()` | Dynamic data engine |
|
|
60
60
|
|
|
61
61
|
### Middleware
|
|
62
62
|
|
|
@@ -94,7 +94,7 @@ serve(app, { port: 3000 })
|
|
|
94
94
|
|
|
95
95
|
## user
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
Auth, registration, JWT, password management, roles.
|
|
98
98
|
|
|
99
99
|
```ts
|
|
100
100
|
import { user, requireRole } from 'weifuwu'
|
|
@@ -102,24 +102,24 @@ import { user, requireRole } from 'weifuwu'
|
|
|
102
102
|
app.use(postgres())
|
|
103
103
|
app.use(user({ secret: process.env.JWT_SECRET }))
|
|
104
104
|
|
|
105
|
-
//
|
|
105
|
+
// Register
|
|
106
106
|
app.post('/api/register', async (req, ctx) => {
|
|
107
107
|
const result = await ctx.userModule.register(await req.json())
|
|
108
108
|
return Response.json(result)
|
|
109
109
|
})
|
|
110
|
-
//
|
|
110
|
+
// Login
|
|
111
111
|
app.post('/api/login', async (req, ctx) => {
|
|
112
112
|
const { email, password } = await req.json()
|
|
113
113
|
const result = await ctx.userModule.login(email, password)
|
|
114
114
|
if (!result) return new Response('Unauthorized', { status: 401 })
|
|
115
115
|
return Response.json(result)
|
|
116
116
|
})
|
|
117
|
-
//
|
|
117
|
+
// Current user
|
|
118
118
|
app.get('/api/me', async (req, ctx) => {
|
|
119
119
|
if (!ctx.user) return new Response('Unauthorized', { status: 401 })
|
|
120
120
|
return Response.json(ctx.user)
|
|
121
121
|
})
|
|
122
|
-
//
|
|
122
|
+
// Admin only
|
|
123
123
|
app.get('/api/admin/users', requireRole('admin'), async (req, ctx) => {
|
|
124
124
|
return Response.json(await ctx.userModule.listUsers())
|
|
125
125
|
})
|
|
@@ -129,28 +129,28 @@ app.get('/api/admin/users', requireRole('admin'), async (req, ctx) => {
|
|
|
129
129
|
|
|
130
130
|
| Method | Returns | Description |
|
|
131
131
|
|--------|---------|-------------|
|
|
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` |
|
|
132
|
+
| `register(input)` | `{ user, token }` | Register |
|
|
133
|
+
| `login(email, pw)` | `{ user, token } \| null` | Login |
|
|
134
|
+
| `getUserById(id)` | `UserRecord \| null` | Get by ID |
|
|
135
|
+
| `getUserByEmail(email)` | `UserRecord \| null` | Get by email |
|
|
136
|
+
| `updateUser(id, input)` | `UserRecord \| null` | Update |
|
|
137
|
+
| `changePassword(id, oldPw, newPw)` | `boolean` | Change password |
|
|
138
|
+
| `deleteUser(id)` | `boolean` | Soft delete |
|
|
139
|
+
| `listUsers(inactive?)` | `UserRecord[]` | List users |
|
|
140
|
+
| `generateToken(user)` | `string` | Issue JWT |
|
|
141
|
+
| `verifyToken(token)` | `TokenPayload \| null` | Verify JWT |
|
|
142
|
+
| `refreshToken(token)` | `string \| null` | Refresh JWT |
|
|
143
143
|
|
|
144
144
|
### ctx.user
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
Auto-resolved from `Authorization: Bearer` or `token` cookie.
|
|
147
147
|
|
|
148
148
|
```ts
|
|
149
149
|
interface User {
|
|
150
150
|
id: string
|
|
151
151
|
name: string
|
|
152
152
|
email: string
|
|
153
|
-
role: string
|
|
153
|
+
role: string
|
|
154
154
|
[key: string]: unknown
|
|
155
155
|
}
|
|
156
156
|
```
|
|
@@ -159,19 +159,19 @@ interface User {
|
|
|
159
159
|
|
|
160
160
|
```ts
|
|
161
161
|
app.get('/admin', requireRole('admin'), handler)
|
|
162
|
-
//
|
|
162
|
+
// No auth → 401, wrong role → 403
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
-
###
|
|
165
|
+
### Security
|
|
166
166
|
|
|
167
|
-
-
|
|
168
|
-
-
|
|
167
|
+
- Password: scrypt + 32-byte random salt
|
|
168
|
+
- Token: HMAC SHA-256, 7 day expiry
|
|
169
169
|
|
|
170
170
|
---
|
|
171
171
|
|
|
172
172
|
## messager
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
Instant messaging + AI conversation layer. Direct/group chat, message persistence, WebSocket push.
|
|
175
175
|
|
|
176
176
|
```ts
|
|
177
177
|
import { messager } from 'weifuwu'
|
|
@@ -180,7 +180,7 @@ app.use(postgres())
|
|
|
180
180
|
app.use(user())
|
|
181
181
|
app.use(messager())
|
|
182
182
|
|
|
183
|
-
// WebSocket —
|
|
183
|
+
// WebSocket — auto-join all user conversations
|
|
184
184
|
app.ws('/ws', {
|
|
185
185
|
async open(ws, ctx) {
|
|
186
186
|
for (const c of await ctx.messager.getConversations()) {
|
|
@@ -209,32 +209,32 @@ app.get('/api/conversations/:id/messages', async (req, ctx) => {
|
|
|
209
209
|
|
|
210
210
|
| Method | Returns | Description |
|
|
211
211
|
|--------|---------|-------------|
|
|
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` |
|
|
212
|
+
| `createDirectConversation(userId)` | `Conversation` | Create/reuse DM |
|
|
213
|
+
| `createGroupConversation(title, userIds)` | `Conversation` | Create group |
|
|
214
|
+
| `sendMessage(convId, body)` | `Message` | Send, auto-broadcast to `conversation:{id}` room |
|
|
215
|
+
| `getMessages(convId, opts?)` | `Message[]` | Cursor pagination |
|
|
216
|
+
| `editMessage(msgId, body)` | `Message \| null` | Edit (24h window) |
|
|
217
|
+
| `deleteMessage(msgId)` | `boolean` | Soft delete |
|
|
218
|
+
| `getConversations()` | `Conversation[]` | List with unread + last message |
|
|
219
|
+
| `getConversation(id)` | `Conversation \| null` | Get detail |
|
|
220
|
+
| `markRead(convId)` | `void` | Mark as read |
|
|
221
|
+
| `getUnreadCount()` | `{ total, byConversation }` | Unread stats |
|
|
222
|
+
| `addParticipants(convId, userIds)` | `void` | Add members |
|
|
223
|
+
| `removeParticipant(convId, userId?)` | `boolean` | Leave / kick |
|
|
224
224
|
|
|
225
|
-
###
|
|
225
|
+
### Storage
|
|
226
226
|
|
|
227
|
-
3
|
|
227
|
+
3 tables: `conversations` / `participants` / `messages`. Auto-migration.
|
|
228
228
|
|
|
229
|
-
### AI
|
|
229
|
+
### AI Conversations
|
|
230
230
|
|
|
231
|
-
messager + agent
|
|
231
|
+
messager + agent = ChatGPT foundation. Messager handles sessions + push, agent handles LLM generation.
|
|
232
232
|
|
|
233
233
|
---
|
|
234
234
|
|
|
235
235
|
## kb
|
|
236
236
|
|
|
237
|
-
RAG
|
|
237
|
+
RAG knowledge base. Import docs → auto-chunk → DashScope embedding → pgvector storage → semantic search.
|
|
238
238
|
|
|
239
239
|
```ts
|
|
240
240
|
import { kb } from 'weifuwu'
|
|
@@ -242,14 +242,14 @@ import { kb } from 'weifuwu'
|
|
|
242
242
|
app.use(postgres())
|
|
243
243
|
app.use(kb())
|
|
244
244
|
|
|
245
|
-
//
|
|
245
|
+
// Import
|
|
246
246
|
app.post('/api/kb/import', async (req, ctx) => {
|
|
247
247
|
const { title, content } = await req.json()
|
|
248
248
|
const result = await ctx.kb.importText(title, content)
|
|
249
249
|
return Response.json(result, { status: 201 })
|
|
250
250
|
})
|
|
251
251
|
|
|
252
|
-
//
|
|
252
|
+
// Search
|
|
253
253
|
app.post('/api/kb/search', async (req, ctx) => {
|
|
254
254
|
const { query } = await req.json()
|
|
255
255
|
return Response.json(await ctx.kb.search(query, { limit: 5 }))
|
|
@@ -260,30 +260,30 @@ app.post('/api/kb/search', async (req, ctx) => {
|
|
|
260
260
|
|
|
261
261
|
| Method | Returns | Description |
|
|
262
262
|
|--------|---------|-------------|
|
|
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` |
|
|
263
|
+
| `importText(title, text, opts?)` | `{ document, chunks }` | Import → chunk → embed → store |
|
|
264
|
+
| `importDocuments(docs)` | `Document[]` | Batch import |
|
|
265
|
+
| `search(query, opts?)` | `SearchResult[]` | Semantic search (cosine) |
|
|
266
|
+
| `list()` | `Document[]` | List documents |
|
|
267
|
+
| `get(id)` | `Document \| null` | Get document |
|
|
268
|
+
| `getChunks(documentId)` | `Chunk[]` | Get chunks |
|
|
269
|
+
| `delete(id)` | `boolean` | Delete + cascade chunks |
|
|
270
270
|
|
|
271
|
-
###
|
|
271
|
+
### Configuration
|
|
272
272
|
|
|
273
273
|
```ts
|
|
274
|
-
//
|
|
274
|
+
// Default: DashScope text-embedding-v4 (env: DASHSCOPE_API_KEY)
|
|
275
275
|
app.use(kb())
|
|
276
276
|
|
|
277
|
-
//
|
|
277
|
+
// Custom embedding
|
|
278
278
|
app.use(kb({
|
|
279
|
-
embed: async (text) => { /*
|
|
279
|
+
embed: async (text) => { /* return number[] */ },
|
|
280
280
|
dimensions: 1536,
|
|
281
|
-
chunkSize: 512, //
|
|
282
|
-
chunkOverlap: 64,
|
|
281
|
+
chunkSize: 512, // tokens
|
|
282
|
+
chunkOverlap: 64,
|
|
283
283
|
}))
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
-
###
|
|
286
|
+
### Integration with Agent
|
|
287
287
|
|
|
288
288
|
```ts
|
|
289
289
|
app.use(agent({
|
|
@@ -294,16 +294,16 @@ app.use(agent({
|
|
|
294
294
|
}))
|
|
295
295
|
```
|
|
296
296
|
|
|
297
|
-
###
|
|
297
|
+
### Storage
|
|
298
298
|
|
|
299
|
-
- `kb_documents` —
|
|
300
|
-
- `kb_chunks` —
|
|
299
|
+
- `kb_documents` — document metadata
|
|
300
|
+
- `kb_chunks` — chunk content + VECTOR(1536) + TSVECTOR GIN index
|
|
301
301
|
|
|
302
302
|
---
|
|
303
303
|
|
|
304
304
|
## agent
|
|
305
305
|
|
|
306
|
-
AI Agent — LLM
|
|
306
|
+
AI Agent — LLM chat, tool calling, RAG, streaming.
|
|
307
307
|
|
|
308
308
|
```ts
|
|
309
309
|
import { agent } from 'weifuwu'
|
|
@@ -327,13 +327,13 @@ app.use(agent({
|
|
|
327
327
|
maxSteps: 5,
|
|
328
328
|
}))
|
|
329
329
|
|
|
330
|
-
//
|
|
330
|
+
// Streaming
|
|
331
331
|
app.post('/api/chat', async (req, ctx) => {
|
|
332
332
|
const { messages } = await req.json()
|
|
333
333
|
return ctx.agent.chatStreamResponse({ messages })
|
|
334
334
|
})
|
|
335
335
|
|
|
336
|
-
//
|
|
336
|
+
// Non-streaming
|
|
337
337
|
app.post('/api/chat/sync', async (req, ctx) => {
|
|
338
338
|
const { prompt } = await req.json()
|
|
339
339
|
const text = await ctx.agent.chat(prompt)
|
|
@@ -345,30 +345,30 @@ app.post('/api/chat/sync', async (req, ctx) => {
|
|
|
345
345
|
|
|
346
346
|
| Method | Description |
|
|
347
347
|
|--------|-------------|
|
|
348
|
-
| `chat(prompt, opts?)` |
|
|
349
|
-
| `chatStreamResponse({ messages })` | SSE
|
|
348
|
+
| `chat(prompt, opts?)` | Non-streaming, returns text |
|
|
349
|
+
| `chatStreamResponse({ messages })` | SSE stream (compatible with `useChat`) |
|
|
350
350
|
|
|
351
|
-
###
|
|
351
|
+
### Default Model
|
|
352
352
|
|
|
353
|
-
- LLM: DeepSeek-V4-Flash
|
|
354
|
-
-
|
|
355
|
-
-
|
|
353
|
+
- LLM: DeepSeek-V4-Flash (via `@ai-sdk/openai` + `baseURL: 'https://api.deepseek.com/v1'`)
|
|
354
|
+
- Override with `DEEPSEEK_MODEL` env
|
|
355
|
+
- API key via `DEEPSEEK_API_KEY` or `OPENAI_API_KEY` env
|
|
356
356
|
|
|
357
357
|
### Features
|
|
358
358
|
|
|
359
359
|
| Feature | Description |
|
|
360
360
|
|---------|-------------|
|
|
361
|
-
| `knowledge.search` | RAG
|
|
362
|
-
| `tools` |
|
|
363
|
-
| `sandbox: true` |
|
|
364
|
-
| `store` |
|
|
365
|
-
| `agents` |
|
|
361
|
+
| `knowledge.search` | RAG callback, auto-injected into system prompt |
|
|
362
|
+
| `tools` | Tool definitions, auto-loop (maxSteps) |
|
|
363
|
+
| `sandbox: true` | Integrates with `ctx.sandbox` |
|
|
364
|
+
| `store` | Session persistence (save/load) |
|
|
365
|
+
| `agents` | Multi-agent orchestration |
|
|
366
366
|
|
|
367
367
|
---
|
|
368
368
|
|
|
369
369
|
## cms
|
|
370
370
|
|
|
371
|
-
|
|
371
|
+
Content management — blog, docs, changelog.
|
|
372
372
|
|
|
373
373
|
```ts
|
|
374
374
|
import { cms, requireRole } from 'weifuwu'
|
|
@@ -377,7 +377,7 @@ app.use(postgres())
|
|
|
377
377
|
app.use(user())
|
|
378
378
|
app.use(cms())
|
|
379
379
|
|
|
380
|
-
//
|
|
380
|
+
// Public
|
|
381
381
|
app.get('/api/posts', async (req, ctx) => {
|
|
382
382
|
return Response.json(await ctx.cms.list({ type: 'post', status: 'published' }))
|
|
383
383
|
})
|
|
@@ -387,7 +387,7 @@ app.get('/api/posts/:slug', async (req, ctx) => {
|
|
|
387
387
|
return Response.json(post)
|
|
388
388
|
})
|
|
389
389
|
|
|
390
|
-
//
|
|
390
|
+
// Admin
|
|
391
391
|
app.post('/api/admin/posts', requireRole('admin'), async (req, ctx) => {
|
|
392
392
|
const post = await ctx.cms.create(await req.json())
|
|
393
393
|
return Response.json(post, { status: 201 })
|
|
@@ -403,31 +403,31 @@ app.patch('/api/admin/posts/:id', requireRole('admin'), async (req, ctx) => {
|
|
|
403
403
|
|
|
404
404
|
| Method | Returns | Description |
|
|
405
405
|
|--------|---------|-------------|
|
|
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` |
|
|
406
|
+
| `create(input)` | `Content` | Create (admin) |
|
|
407
|
+
| `get(slug)` | `Content \| null` | Get by slug |
|
|
408
|
+
| `getById(id)` | `Content \| null` | Get by ID |
|
|
409
|
+
| `update(id, input)` | `Content \| null` | Update (admin) |
|
|
410
|
+
| `delete(id)` | `boolean` | Delete (admin) |
|
|
411
|
+
| `list(opts?)` | `Content[]` | List with cursor |
|
|
412
|
+
| `publish(id)` | `Content \| null` | Publish (admin) |
|
|
413
|
+
| `unpublish(id)` | `Content \| null` | Unpublish (admin) |
|
|
414
|
+
| `listTags()` | `TagWithCount[]` | List tags |
|
|
415
|
+
| `createTag(name)` | `Tag` | Create tag |
|
|
416
416
|
|
|
417
417
|
### Features
|
|
418
418
|
|
|
419
|
-
-
|
|
420
|
-
-
|
|
421
|
-
- Slug
|
|
422
|
-
-
|
|
423
|
-
-
|
|
424
|
-
-
|
|
419
|
+
- Types: post / page / doc / changelog (any string)
|
|
420
|
+
- Status: draft / published / archived
|
|
421
|
+
- Slug: auto-generated, unique per type
|
|
422
|
+
- Tags: many-to-many, auto-created
|
|
423
|
+
- Tree: parent_id for hierarchy
|
|
424
|
+
- Auth: non-admin users see published only
|
|
425
425
|
|
|
426
426
|
---
|
|
427
427
|
|
|
428
428
|
## base
|
|
429
429
|
|
|
430
|
-
|
|
430
|
+
Dynamic data storage engine — let users define their own data structures (like Airtable).
|
|
431
431
|
|
|
432
432
|
```ts
|
|
433
433
|
import { base } from 'weifuwu'
|
|
@@ -436,13 +436,13 @@ app.use(postgres())
|
|
|
436
436
|
app.use(user())
|
|
437
437
|
app.use(base())
|
|
438
438
|
|
|
439
|
-
//
|
|
439
|
+
// Define schema
|
|
440
440
|
app.post('/api/bases', async (req, ctx) => {
|
|
441
441
|
const b = await ctx.base.create(await req.json())
|
|
442
442
|
return Response.json(b, { status: 201 })
|
|
443
443
|
})
|
|
444
444
|
|
|
445
|
-
//
|
|
445
|
+
// CRUD
|
|
446
446
|
app.post('/api/bases/:id/:table', async (req, ctx) => {
|
|
447
447
|
const row = await ctx.base.insert(ctx.params.id, ctx.params.table, await req.json())
|
|
448
448
|
return Response.json(row, { status: 201 })
|
|
@@ -461,35 +461,37 @@ app.get('/api/bases/:id/:table', async (req, ctx) => {
|
|
|
461
461
|
|
|
462
462
|
| Method | Returns | Description |
|
|
463
463
|
|--------|---------|-------------|
|
|
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
|
-
|
|
464
|
+
| `create({ name, tables })` | `BaseDef` | Create database |
|
|
465
|
+
| `defineTable(baseId, schema)` | `BaseDef` | Add table |
|
|
466
|
+
| `updateTable(baseId, name, schema)` | `BaseDef \| null` | Update table |
|
|
467
|
+
| `removeTable(baseId, name)` | `BaseDef \| null` | Remove table |
|
|
468
|
+
| `insert(baseId, table, data)` | `Row` | Insert row |
|
|
469
|
+
| `getRow(baseId, table, id)` | `Row \| null` | Get row |
|
|
470
|
+
| `updateRow(baseId, table, id, data)` | `Row \| null` | Update row |
|
|
471
|
+
| `deleteRow(baseId, table, id)` | `boolean` | Delete row |
|
|
472
|
+
| `query(baseId, table, opts?)` | `Row[]` | Query (filter/sort/limit/offset) |
|
|
473
|
+
| `search(baseId, table, field, query)` | `Row[]` | Full-text search |
|
|
474
|
+
| `similaritySearch(baseId, table, field, vector)` | `Row[]` | Vector search |
|
|
475
|
+
| `list()` / `get(id)` / `getBySlug(slug)` / `delete(id)` | — | Manage databases |
|
|
476
|
+
|
|
477
|
+
### Architecture
|
|
478
|
+
|
|
479
|
+
Fixed Slot: a single `base_data` table with ~120 physical columns:
|
|
480
|
+
|
|
481
|
+
| Type | Count | PG Type |
|
|
482
|
+
|------|:-----:|:--------:|
|
|
483
483
|
| text001..064 | 64 | TEXT |
|
|
484
484
|
| number001..032 | 32 | DOUBLE PRECISION |
|
|
485
485
|
| date001..008 | 8 | TIMESTAMPTZ |
|
|
486
486
|
| vector001..004 | 4 | VECTOR(1536) |
|
|
487
487
|
| search001..004 | 4 | TEXT |
|
|
488
|
-
| ext | 1 | JSONB
|
|
488
|
+
| ext | 1 | JSONB (overflow) |
|
|
489
489
|
|
|
490
|
-
|
|
490
|
+
Field name → physical column mapping stored in `base_column_map`. Fields beyond the physical columns overflow to ext JSONB.
|
|
491
491
|
|
|
492
|
-
pgvector
|
|
492
|
+
pgvector auto-detected (included in docker image).
|
|
493
|
+
|
|
494
|
+
---
|
|
493
495
|
|
|
494
496
|
---
|
|
495
497
|
|
|
@@ -507,12 +509,12 @@ app.all(path, ...handlers)
|
|
|
507
509
|
app.ws(path, ...middlewares, handler)
|
|
508
510
|
// handler: { open?, message?, close?, error? }
|
|
509
511
|
|
|
510
|
-
//
|
|
512
|
+
// Middleware & mounting
|
|
511
513
|
app.use(middleware)
|
|
512
514
|
app.mount(prefix, router)
|
|
513
515
|
app.plugin(fn)
|
|
514
516
|
app.onError(handler)
|
|
515
|
-
app.routes() //
|
|
517
|
+
app.routes() // debug: list all routes
|
|
516
518
|
```
|
|
517
519
|
|
|
518
520
|
---
|
|
@@ -546,7 +548,7 @@ await sql.sql`SELECT * FROM users WHERE id = ${id}`
|
|
|
546
548
|
await sql.sql.begin(async (sql) => { /* transaction */ })
|
|
547
549
|
```
|
|
548
550
|
|
|
549
|
-
`DATABASE_URL`
|
|
551
|
+
Reads `DATABASE_URL` env. Supports migrations, transactions, connection pool stats.
|
|
550
552
|
|
|
551
553
|
---
|
|
552
554
|
|
|
@@ -560,7 +562,7 @@ app.use(r) // → ctx.redis
|
|
|
560
562
|
await r.redis.set('key', 'value')
|
|
561
563
|
```
|
|
562
564
|
|
|
563
|
-
`REDIS_URL`
|
|
565
|
+
Reads `REDIS_URL` env (default: `redis://localhost:6379`).
|
|
564
566
|
|
|
565
567
|
---
|
|
566
568
|
|
|
@@ -585,20 +587,19 @@ q.run()
|
|
|
585
587
|
|
|
586
588
|
```
|
|
587
589
|
src/
|
|
588
|
-
├── index.ts ←
|
|
589
|
-
├── types.ts ← Context, Handler, Middleware
|
|
590
|
+
├── index.ts ← Entry, exports all modules
|
|
591
|
+
├── types.ts ← Context, Handler, Middleware types
|
|
590
592
|
├── core/ ← serve, router, ws, trace, logger
|
|
591
593
|
├── 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
|
|
594
|
+
├── user/ ← User system (CRUD, JWT, requireRole)
|
|
595
|
+
├── messager/ ← IM + AI conversation layer
|
|
596
|
+
├── kb/ ← RAG knowledge base (chunking, embedding, vector search)
|
|
597
|
+
├── ai/ ← AI Agent (LLM, tools, RAG)
|
|
598
|
+
├── cms/ ← Content management (blog, docs, changelog)
|
|
599
|
+
├── base/ ← Dynamic data engine (Fixed Slot)
|
|
600
|
+
├── postgres/ ← PostgreSQL client
|
|
601
|
+
├── redis/ ← Redis client
|
|
602
|
+
├── queue/ ← Job queue + cron
|
|
602
603
|
├── graphql.ts ← GraphQL
|
|
603
604
|
├── hub.ts ← WebSocket hub
|
|
604
605
|
└── test/ ← 281 tests
|
|
@@ -611,7 +612,7 @@ docker-compose.yml ← postgres (pgvector) + redis
|
|
|
611
612
|
## Development
|
|
612
613
|
|
|
613
614
|
```bash
|
|
614
|
-
docker compose up -d #
|
|
615
|
+
docker compose up -d # Start postgres + redis
|
|
615
616
|
npm run build # esbuild → dist/
|
|
616
617
|
npm run typecheck # tsc --noEmit
|
|
617
618
|
npm test # 281 tests
|
package/dist/index.d.ts
CHANGED
|
@@ -37,9 +37,6 @@ export { createHub } from './hub.ts';
|
|
|
37
37
|
export type { Hub, HubOptions } from './hub.ts';
|
|
38
38
|
export { queue } from './queue/index.ts';
|
|
39
39
|
export type { QueueOptions, QueueJob, Queue, QueueInjected } from './queue/types.ts';
|
|
40
|
-
export { react, reactRouter } from './react/index.ts';
|
|
41
|
-
export type { ReactOptions, RenderOptions, ReactRouterOptions, ReactAppOptions } from './react/types.ts';
|
|
42
|
-
export { useServerData, ServerDataContext, Link, ErrorBoundary } from './react/index.ts';
|
|
43
40
|
export { ai } from './ai/index.ts';
|
|
44
41
|
export type { AiOptions, Ai } from './ai/index.ts';
|
|
45
42
|
export { agent } from './ai/agent.ts';
|