weifuwu 0.16.0 → 0.16.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 +11 -9
- package/cli.ts +13 -16
- package/dist/ai-sdk.d.ts +2 -0
- package/dist/cli.js +12 -13
- package/dist/iii/ws.d.ts +1 -0
- package/dist/index.d.ts +11 -10
- package/dist/index.js +84 -37
- package/dist/server.d.ts +1 -0
- package/package.json +1 -2
- package/README.zh.md +0 -158
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Everything follows the same `(req, ctx) => Response` contract. The Router handle
|
|
|
24
24
|
- **PostgreSQL** — schema builder with type-safe DDL, CRUD (`read`/`readMany`, `insertMany`, `update`/`updateMany`, `delete`/`deleteMany`), WHERE helpers (`eq`, `gte`, `contains`, `and`, `or`), transactions, vector search
|
|
25
25
|
- **Auth** — password + JWT + OAuth2 Server (authorization code / PKCE / client_credentials)
|
|
26
26
|
- **Real-time** — WebSocket, messaging channels with agent routing
|
|
27
|
-
- **AI** — streaming endpoint, DAG workflow tool, AI agents with RAG and tool-use
|
|
27
|
+
- **AI** — streaming endpoint, DAG workflow tool, AI agents with RAG and tool-use — re-exports `streamText`, `tool`, `openai` and more from AI SDK
|
|
28
28
|
- **Data** — Redis client, job queue with cron scheduling
|
|
29
29
|
- **Multi-tenant BaaS** — dynamic tables, auto REST + GraphQL, row-level isolation
|
|
30
30
|
- **Deploy** — self-hosted PaaS: multi-app proxy, zero-downtime updates, auto SSL
|
|
@@ -49,8 +49,7 @@ serve((req, ctx) => new Response('Hello, World!'), { port: 3000 })
|
|
|
49
49
|
### Full app
|
|
50
50
|
|
|
51
51
|
```ts
|
|
52
|
-
import { serve, Router, postgres, user, aiStream, graphql } from 'weifuwu'
|
|
53
|
-
import { openai } from '@ai-sdk/openai'
|
|
52
|
+
import { serve, Router, postgres, user, aiStream, graphql, openai } from 'weifuwu'
|
|
54
53
|
|
|
55
54
|
const app = new Router()
|
|
56
55
|
const pg = postgres()
|
|
@@ -147,6 +146,11 @@ All use the same pattern — `const m = module(options)` → `app.use('/path', m
|
|
|
147
146
|
| `serial()`, `uuid()`, `text()`, ... | Column type builders |
|
|
148
147
|
| `eq()`, `gte()`, `contains()`, `and()` ... | WHERE clause helpers — same API as Drizzle |
|
|
149
148
|
| `PgModule` | Base class for DB-backed modules |
|
|
149
|
+
| `streamText()` / `generateText()` / `streamObject()` / `generateObject()` | AI SDK — text/structured generation |
|
|
150
|
+
| `tool()` | AI SDK — tool definition |
|
|
151
|
+
| `embed()` / `embedMany()` | AI SDK — text embeddings |
|
|
152
|
+
| `smoothStream()` | AI SDK — smooth streaming middleware |
|
|
153
|
+
| `openai` / `createOpenAI()` | OpenAI provider for AI SDK |
|
|
150
154
|
|
|
151
155
|
---
|
|
152
156
|
|
|
@@ -155,7 +159,7 @@ All use the same pattern — `const m = module(options)` → `app.use('/path', m
|
|
|
155
159
|
Optional module that organizes service logic as **Worker + Function + Trigger**, plus a pure WebSocket SDK for connecting remote workers. Built-in `stream::*` functions for hierarchical real-time data.
|
|
156
160
|
|
|
157
161
|
```ts
|
|
158
|
-
import { serve, Router, iii, createWorker
|
|
162
|
+
import { serve, Router, iii, createWorker, registerWorker } from 'weifuwu'
|
|
159
163
|
|
|
160
164
|
// Engine
|
|
161
165
|
const engine = iii({ pg, redis })
|
|
@@ -164,7 +168,7 @@ app.use('/iii', engine.router())
|
|
|
164
168
|
serve(app.handler(), { port: 3000, websocket: app.websocketHandler() })
|
|
165
169
|
|
|
166
170
|
// Local worker
|
|
167
|
-
const w =
|
|
171
|
+
const w = createWorker('orders')
|
|
168
172
|
w.registerFunction('orders::create', async (payload) => {
|
|
169
173
|
return db.query('INSERT INTO orders ...', [payload.items])
|
|
170
174
|
})
|
|
@@ -1096,8 +1100,7 @@ export default function NotFound() {
|
|
|
1096
1100
|
Server-sent event streaming via the Vercel AI SDK:
|
|
1097
1101
|
|
|
1098
1102
|
```ts
|
|
1099
|
-
import { serve, Router, aiStream } from 'weifuwu'
|
|
1100
|
-
import { openai } from '@ai-sdk/openai'
|
|
1103
|
+
import { serve, Router, aiStream, openai } from 'weifuwu'
|
|
1101
1104
|
|
|
1102
1105
|
const app = new Router()
|
|
1103
1106
|
const chat = await aiStream(async (req, ctx) => {
|
|
@@ -1114,8 +1117,7 @@ serve(app.handler(), { port: 3000 })
|
|
|
1114
1117
|
Multi-step DAG execution engine — packaged as a single AI SDK `Tool`. Use it with `streamText()` or `generateText()` when the LLM needs conditional logic, loops, or multi-step tool orchestration.
|
|
1115
1118
|
|
|
1116
1119
|
```ts
|
|
1117
|
-
import { tool, streamText } from '
|
|
1118
|
-
import { runWorkflow } from 'weifuwu'
|
|
1120
|
+
import { tool, streamText, runWorkflow } from 'weifuwu'
|
|
1119
1121
|
import { z } from 'zod'
|
|
1120
1122
|
|
|
1121
1123
|
const tools = {
|
package/cli.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { mkdir, writeFile, copyFile,
|
|
2
|
+
import { mkdir, writeFile, copyFile, readFile } from 'node:fs/promises'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
3
4
|
import { homedir } from 'node:os'
|
|
4
5
|
import { join, dirname, resolve } from 'node:path'
|
|
5
6
|
import { fileURLToPath } from 'node:url'
|
|
@@ -7,26 +8,20 @@ import { fileURLToPath } from 'node:url'
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url)
|
|
8
9
|
const __dirname = dirname(__filename)
|
|
9
10
|
|
|
10
|
-
const pkgRoot = resolve(__dirname, '..')
|
|
11
|
+
const pkgRoot = existsSync(join(__dirname, 'package.json')) ? __dirname : resolve(__dirname, '..')
|
|
11
12
|
|
|
12
13
|
async function cmdSkill() {
|
|
13
14
|
const targetDir = join(homedir(), '.agents', 'skills', 'weifuwu')
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
await mkdir(docsTarget, { recursive: true })
|
|
15
|
+
await mkdir(targetDir, { recursive: true })
|
|
17
16
|
await copyFile(join(pkgRoot, 'README.md'), join(targetDir, 'SKILL.md'))
|
|
18
|
-
|
|
19
|
-
const docDir = join(pkgRoot, 'docs')
|
|
20
|
-
const entries = await readdir(docDir)
|
|
21
|
-
for (const entry of entries) {
|
|
22
|
-
if (entry.endsWith('.md')) {
|
|
23
|
-
await copyFile(join(docDir, entry), join(docsTarget, entry))
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
17
|
console.log('✅ Installed weifuwu skill to ~/.agents/skills/weifuwu/')
|
|
28
18
|
}
|
|
29
19
|
|
|
20
|
+
async function cmdVersion() {
|
|
21
|
+
const pkg = JSON.parse(await readFile(join(pkgRoot, 'package.json'), 'utf-8'))
|
|
22
|
+
console.log(pkg.version)
|
|
23
|
+
}
|
|
24
|
+
|
|
30
25
|
async function cmdInit(name: string) {
|
|
31
26
|
const targetDir = resolve(process.cwd(), name)
|
|
32
27
|
await mkdir(targetDir, { recursive: true })
|
|
@@ -78,7 +73,9 @@ async function cmdInit(name: string) {
|
|
|
78
73
|
|
|
79
74
|
const cmd = process.argv[2]
|
|
80
75
|
|
|
81
|
-
if (cmd === '
|
|
76
|
+
if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
|
|
77
|
+
cmdVersion().catch(console.error)
|
|
78
|
+
} else if (cmd === 'skill') {
|
|
82
79
|
cmdSkill().catch(console.error)
|
|
83
80
|
} else if (cmd === 'init') {
|
|
84
81
|
const name = process.argv[3]
|
|
@@ -88,5 +85,5 @@ if (cmd === 'skill') {
|
|
|
88
85
|
}
|
|
89
86
|
cmdInit(name).catch(console.error)
|
|
90
87
|
} else {
|
|
91
|
-
console.log('\nUsage:\n npx weifuwu init <name> Create a new weifuwu project\n npx weifuwu skill Install weifuwu skill to ~/.agents/skills/\n')
|
|
88
|
+
console.log('\nUsage:\n npx weifuwu version Print version\n npx weifuwu init <name> Create a new weifuwu project\n npx weifuwu skill Install weifuwu skill to ~/.agents/skills/\n')
|
|
92
89
|
}
|
package/dist/ai-sdk.d.ts
ADDED
package/dist/cli.js
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// cli.ts
|
|
4
|
-
import { mkdir, writeFile, copyFile,
|
|
4
|
+
import { mkdir, writeFile, copyFile, readFile } from "node:fs/promises";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
5
6
|
import { homedir } from "node:os";
|
|
6
7
|
import { join, dirname, resolve } from "node:path";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
var __filename = fileURLToPath(import.meta.url);
|
|
9
10
|
var __dirname = dirname(__filename);
|
|
10
|
-
var pkgRoot = resolve(__dirname, "..");
|
|
11
|
+
var pkgRoot = existsSync(join(__dirname, "package.json")) ? __dirname : resolve(__dirname, "..");
|
|
11
12
|
async function cmdSkill() {
|
|
12
13
|
const targetDir = join(homedir(), ".agents", "skills", "weifuwu");
|
|
13
|
-
|
|
14
|
-
await mkdir(docsTarget, { recursive: true });
|
|
14
|
+
await mkdir(targetDir, { recursive: true });
|
|
15
15
|
await copyFile(join(pkgRoot, "README.md"), join(targetDir, "SKILL.md"));
|
|
16
|
-
const docDir = join(pkgRoot, "docs");
|
|
17
|
-
const entries = await readdir(docDir);
|
|
18
|
-
for (const entry of entries) {
|
|
19
|
-
if (entry.endsWith(".md")) {
|
|
20
|
-
await copyFile(join(docDir, entry), join(docsTarget, entry));
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
16
|
console.log("\u2705 Installed weifuwu skill to ~/.agents/skills/weifuwu/");
|
|
24
17
|
}
|
|
18
|
+
async function cmdVersion() {
|
|
19
|
+
const pkg = JSON.parse(await readFile(join(pkgRoot, "package.json"), "utf-8"));
|
|
20
|
+
console.log(pkg.version);
|
|
21
|
+
}
|
|
25
22
|
async function cmdInit(name) {
|
|
26
23
|
const targetDir = resolve(process.cwd(), name);
|
|
27
24
|
await mkdir(targetDir, { recursive: true });
|
|
@@ -65,7 +62,9 @@ async function cmdInit(name) {
|
|
|
65
62
|
console.log(`\u2705 Created ${name}/ \u2014 cd ${name} && npm install && npm run dev`);
|
|
66
63
|
}
|
|
67
64
|
var cmd = process.argv[2];
|
|
68
|
-
if (cmd === "
|
|
65
|
+
if (cmd === "version" || cmd === "-v" || cmd === "--version") {
|
|
66
|
+
cmdVersion().catch(console.error);
|
|
67
|
+
} else if (cmd === "skill") {
|
|
69
68
|
cmdSkill().catch(console.error);
|
|
70
69
|
} else if (cmd === "init") {
|
|
71
70
|
const name = process.argv[3];
|
|
@@ -75,5 +74,5 @@ if (cmd === "skill") {
|
|
|
75
74
|
}
|
|
76
75
|
cmdInit(name).catch(console.error);
|
|
77
76
|
} else {
|
|
78
|
-
console.log("\nUsage:\n npx weifuwu init <name> Create a new weifuwu project\n npx weifuwu skill Install weifuwu skill to ~/.agents/skills/\n");
|
|
77
|
+
console.log("\nUsage:\n npx weifuwu version Print version\n npx weifuwu init <name> Create a new weifuwu project\n npx weifuwu skill Install weifuwu skill to ~/.agents/skills/\n");
|
|
79
78
|
}
|
package/dist/iii/ws.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ interface WsHandlerDeps {
|
|
|
18
18
|
removeStreamSubscriber: (ws: WebSocket) => void;
|
|
19
19
|
handleInvokeResult: (invocationId: string, result: unknown) => void;
|
|
20
20
|
handleInvokeError: (invocationId: string, error: string) => void;
|
|
21
|
+
handleInvoke: (ws: WebSocket, invocationId: string, functionId: string, payload: unknown) => void;
|
|
21
22
|
}
|
|
22
23
|
export declare function createWsHandler(deps: WsHandlerDeps): {
|
|
23
24
|
open(_ws: WebSocket, _ctx: Context): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -31,33 +31,34 @@ export type { GraphQLOptions, GraphQLHandler } from './graphql.ts';
|
|
|
31
31
|
export { aiStream } from './ai.ts';
|
|
32
32
|
export type { AIHandler } from './ai.ts';
|
|
33
33
|
export { runWorkflow } from './ai/workflow.ts';
|
|
34
|
+
export { streamText, generateText, generateObject, streamObject, tool, embed, embedMany, smoothStream, openai, createOpenAI, } from './ai-sdk.ts';
|
|
34
35
|
export { postgres } from './postgres/index.ts';
|
|
35
36
|
export type { PostgresOptions, PostgresClient } from './postgres/types.ts';
|
|
36
37
|
export { user } from './user/index.ts';
|
|
37
|
-
export type { UserOptions, UserData, UserModule, OAuth2Client } from './user/types.ts';
|
|
38
|
+
export type { UserOptions, UserData, UserModule, OAuth2Client, } from './user/types.ts';
|
|
38
39
|
export { redis } from './redis/index.ts';
|
|
39
40
|
export type { RedisOptions, RedisClient } from './redis/types.ts';
|
|
40
41
|
export { queue } from './queue/index.ts';
|
|
41
42
|
export type { QueueOptions, QueueJob, Queue } from './queue/types.ts';
|
|
42
43
|
export { tenant } from './tenant/index.ts';
|
|
43
|
-
export type { TenantOptions, TenantModule, TenantContext, FieldDef, FieldType, RelationDef, UserTableRow } from './tenant/types.ts';
|
|
44
|
+
export type { TenantOptions, TenantModule, TenantContext, FieldDef, FieldType, RelationDef, UserTableRow, } from './tenant/types.ts';
|
|
44
45
|
export { agent } from './agent/index.ts';
|
|
45
|
-
export type { AgentOptions, AgentModule, AgentConfig, RunParams, RunResult, KnowledgeDoc } from './agent/types.ts';
|
|
46
|
+
export type { AgentOptions, AgentModule, AgentConfig, RunParams, RunResult, KnowledgeDoc, } from './agent/types.ts';
|
|
46
47
|
export { messager } from './messager/index.ts';
|
|
47
|
-
export type { MessagerOptions, MessagerModule, Channel, ChannelMember, Message } from './messager/types.ts';
|
|
48
|
+
export type { MessagerOptions, MessagerModule, Channel, ChannelMember, Message, } from './messager/types.ts';
|
|
48
49
|
export { deploy, defineConfig } from './deploy/index.ts';
|
|
49
|
-
export type { DeployConfig, AppConfig, DeployServer, AppStatus } from './deploy/types.ts';
|
|
50
|
+
export type { DeployConfig, AppConfig, DeployServer, AppStatus, } from './deploy/types.ts';
|
|
50
51
|
export { opencode } from './opencode/index.ts';
|
|
51
|
-
export type { OpencodeOptions, OpencodeModule, SkillDef, OpencodePermissions, Session as OpencodeSession } from './opencode/types.ts';
|
|
52
|
+
export type { OpencodeOptions, OpencodeModule, SkillDef, OpencodePermissions, Session as OpencodeSession, } from './opencode/types.ts';
|
|
52
53
|
export { health } from './health.ts';
|
|
53
54
|
export type { HealthOptions } from './health.ts';
|
|
54
55
|
export { i18n } from './i18n.ts';
|
|
55
56
|
export type { I18nOptions } from './i18n.ts';
|
|
56
57
|
export { seo, seoMiddleware, seoTags } from './seo.ts';
|
|
57
|
-
export type { SeoOptions, RobotsRule, SitemapUrl, SitemapConfig, SeoHeadersConfig, SeoTagsConfig } from './seo.ts';
|
|
58
|
+
export type { SeoOptions, RobotsRule, SitemapUrl, SitemapConfig, SeoHeadersConfig, SeoTagsConfig, } from './seo.ts';
|
|
58
59
|
export { mailer } from './mailer.ts';
|
|
59
60
|
export type { MailerOptions, MailOptions, Mailer } from './mailer.ts';
|
|
60
61
|
export { logdb } from './logdb/index.ts';
|
|
61
|
-
export type { LogdbOptions, LogdbModule, LogEntry, LogEntryInput } from './logdb/types.ts';
|
|
62
|
-
export { iii, createWorker
|
|
63
|
-
export type { IIIModule, IIIOptions, WorkerInfo, FunctionInfo, TriggerInfo, FunctionHandler, FunctionContext, TriggerInput, RemoteWorker, TriggerRequest } from './iii/types.ts';
|
|
62
|
+
export type { LogdbOptions, LogdbModule, LogEntry, LogEntryInput, } from './logdb/types.ts';
|
|
63
|
+
export { iii, createWorker, registerWorker } from './iii/index.ts';
|
|
64
|
+
export type { IIIModule, IIIOptions, WorkerInfo, FunctionInfo, TriggerInfo, FunctionHandler, FunctionContext, TriggerInput, RemoteWorker, TriggerRequest, } from './iii/types.ts';
|
package/dist/index.js
CHANGED
|
@@ -2109,13 +2109,13 @@ async function aiStream(handler) {
|
|
|
2109
2109
|
r.post("/", async (req, ctx) => {
|
|
2110
2110
|
const options = await handler(req, ctx);
|
|
2111
2111
|
if (options.schema) {
|
|
2112
|
-
const
|
|
2112
|
+
const streamObject2 = await getStreamObject();
|
|
2113
2113
|
const { schema, ...params } = options;
|
|
2114
|
-
const result2 =
|
|
2114
|
+
const result2 = streamObject2({ ...params, schema, output: "object" });
|
|
2115
2115
|
return result2.toTextStreamResponse();
|
|
2116
2116
|
}
|
|
2117
|
-
const
|
|
2118
|
-
const result =
|
|
2117
|
+
const streamText4 = await getStreamText();
|
|
2118
|
+
const result = streamText4(options);
|
|
2119
2119
|
return result.toTextStreamResponse();
|
|
2120
2120
|
});
|
|
2121
2121
|
return { router: () => r };
|
|
@@ -2371,6 +2371,22 @@ function runWorkflow(opts = {}) {
|
|
|
2371
2371
|
});
|
|
2372
2372
|
}
|
|
2373
2373
|
|
|
2374
|
+
// ai-sdk.ts
|
|
2375
|
+
import {
|
|
2376
|
+
streamText,
|
|
2377
|
+
generateText as generateText2,
|
|
2378
|
+
generateObject,
|
|
2379
|
+
streamObject,
|
|
2380
|
+
tool as tool2,
|
|
2381
|
+
embed,
|
|
2382
|
+
embedMany,
|
|
2383
|
+
smoothStream
|
|
2384
|
+
} from "ai";
|
|
2385
|
+
import {
|
|
2386
|
+
openai,
|
|
2387
|
+
createOpenAI
|
|
2388
|
+
} from "@ai-sdk/openai";
|
|
2389
|
+
|
|
2374
2390
|
// postgres/client.ts
|
|
2375
2391
|
import postgresFactory from "postgres";
|
|
2376
2392
|
|
|
@@ -4501,7 +4517,7 @@ function tenant(options) {
|
|
|
4501
4517
|
}
|
|
4502
4518
|
|
|
4503
4519
|
// agent/client.ts
|
|
4504
|
-
import { createOpenAI } from "@ai-sdk/openai";
|
|
4520
|
+
import { createOpenAI as createOpenAI2 } from "@ai-sdk/openai";
|
|
4505
4521
|
|
|
4506
4522
|
// agent/rest.ts
|
|
4507
4523
|
function buildRouter2(deps) {
|
|
@@ -4605,7 +4621,7 @@ function buildRouter2(deps) {
|
|
|
4605
4621
|
}
|
|
4606
4622
|
|
|
4607
4623
|
// agent/run.ts
|
|
4608
|
-
import { streamText, generateText as
|
|
4624
|
+
import { streamText as streamText2, generateText as generateText3, embed as embed2 } from "ai";
|
|
4609
4625
|
import { z as z4 } from "zod";
|
|
4610
4626
|
function hasKnowledgeDocs(sql2, agentId) {
|
|
4611
4627
|
return sql2`SELECT 1 FROM "_knowledge_documents" WHERE agent_id = ${agentId} LIMIT 1`.then((r) => r.length > 0);
|
|
@@ -4625,7 +4641,7 @@ function chunkContent(content, chunkSize = 512, overlap = 64) {
|
|
|
4625
4641
|
return chunks;
|
|
4626
4642
|
}
|
|
4627
4643
|
async function searchKnowledge(sql2, embedModel, agentId, query, limit = 5) {
|
|
4628
|
-
const { embedding } = await
|
|
4644
|
+
const { embedding } = await embed2({ model: embedModel, value: query });
|
|
4629
4645
|
const vec = `[${embedding.join(",")}]`;
|
|
4630
4646
|
const docs = await sql2.unsafe(
|
|
4631
4647
|
`SELECT id, title, content, metadata, embedding <=> $1::vector AS _score FROM "_knowledge_documents" WHERE agent_id = $2 ORDER BY embedding <=> $1::vector LIMIT $3`,
|
|
@@ -4664,13 +4680,13 @@ function createRunner(deps) {
|
|
|
4664
4680
|
};
|
|
4665
4681
|
}
|
|
4666
4682
|
if (agent2.type === "tool-use" && userTools) {
|
|
4667
|
-
for (const [key,
|
|
4668
|
-
tools[key] =
|
|
4683
|
+
for (const [key, tool12] of Object.entries(userTools)) {
|
|
4684
|
+
tools[key] = tool12;
|
|
4669
4685
|
}
|
|
4670
4686
|
}
|
|
4671
4687
|
const system = agent2.system_prompt || void 0;
|
|
4672
4688
|
if (params.stream) {
|
|
4673
|
-
const result =
|
|
4689
|
+
const result = streamText2({
|
|
4674
4690
|
model,
|
|
4675
4691
|
system,
|
|
4676
4692
|
messages: messages2,
|
|
@@ -4693,7 +4709,7 @@ function createRunner(deps) {
|
|
|
4693
4709
|
});
|
|
4694
4710
|
return { stream: sseStream };
|
|
4695
4711
|
} else {
|
|
4696
|
-
const result = await
|
|
4712
|
+
const result = await generateText3({
|
|
4697
4713
|
model,
|
|
4698
4714
|
system,
|
|
4699
4715
|
messages: messages2,
|
|
@@ -4706,14 +4722,14 @@ function createRunner(deps) {
|
|
|
4706
4722
|
const embedModel = getEmbeddingModel();
|
|
4707
4723
|
const chunks = chunkContent(content);
|
|
4708
4724
|
const [first] = chunks;
|
|
4709
|
-
const { embedding } = await
|
|
4725
|
+
const { embedding } = await embed2({ model: embedModel, value: first });
|
|
4710
4726
|
const vec = `[${embedding.join(",")}]`;
|
|
4711
4727
|
const [doc] = await sql2.unsafe(
|
|
4712
4728
|
`INSERT INTO "_knowledge_documents" ("agent_id", "title", "content", "embedding") VALUES ($1, $2, $3, $4::vector) RETURNING *`,
|
|
4713
4729
|
[agentId, title, first, vec]
|
|
4714
4730
|
);
|
|
4715
4731
|
for (let i = 1; i < chunks.length; i++) {
|
|
4716
|
-
const { embedding: emb } = await
|
|
4732
|
+
const { embedding: emb } = await embed2({ model: embedModel, value: chunks[i] });
|
|
4717
4733
|
await sql2.unsafe(
|
|
4718
4734
|
`INSERT INTO "_knowledge_documents" ("agent_id", "title", "content", "embedding") VALUES ($1, $2, $3, $4::vector)`,
|
|
4719
4735
|
[agentId, `${title} (${i + 1})`, chunks[i], `[${emb.join(",")}]`]
|
|
@@ -4730,7 +4746,7 @@ function createModelsFromEnv() {
|
|
|
4730
4746
|
const apiKey = process.env.OPENAI_API_KEY || "ollama";
|
|
4731
4747
|
const modelName = process.env.OPENAI_MODEL || "qwen3:0.6b";
|
|
4732
4748
|
const embedModelName = process.env.OPENAI_EMBEDDING_MODEL || "qwen3-embedding:0.6b";
|
|
4733
|
-
const provider =
|
|
4749
|
+
const provider = createOpenAI2({ baseURL, apiKey });
|
|
4734
4750
|
return {
|
|
4735
4751
|
model: provider(modelName),
|
|
4736
4752
|
embeddingModel: provider.embedding(embedModelName),
|
|
@@ -5685,7 +5701,7 @@ function ensureCertificates(config) {
|
|
|
5685
5701
|
}
|
|
5686
5702
|
|
|
5687
5703
|
// opencode/client.ts
|
|
5688
|
-
import { createOpenAI as
|
|
5704
|
+
import { createOpenAI as createOpenAI3 } from "@ai-sdk/openai";
|
|
5689
5705
|
|
|
5690
5706
|
// opencode/session.ts
|
|
5691
5707
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -5769,13 +5785,13 @@ async function addToolMessages(sql2, sessionId, toolCalls, toolResults) {
|
|
|
5769
5785
|
}
|
|
5770
5786
|
|
|
5771
5787
|
// opencode/run.ts
|
|
5772
|
-
import { streamText as
|
|
5788
|
+
import { streamText as streamText3, stepCountIs } from "ai";
|
|
5773
5789
|
async function* executeGenerator(opts) {
|
|
5774
5790
|
const { sessionId, input, model, tools, systemPrompt, messages: messages2, sql: sql2, abortSignal } = opts;
|
|
5775
5791
|
const lastStepToolCalls = [];
|
|
5776
5792
|
let currentAssistantText = "";
|
|
5777
5793
|
let currentUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
5778
|
-
const result =
|
|
5794
|
+
const result = streamText3({
|
|
5779
5795
|
model,
|
|
5780
5796
|
system: systemPrompt,
|
|
5781
5797
|
messages: [
|
|
@@ -5907,11 +5923,11 @@ function isSkillAllowed(name, perms) {
|
|
|
5907
5923
|
}
|
|
5908
5924
|
|
|
5909
5925
|
// opencode/tools/bash.ts
|
|
5910
|
-
import { tool as
|
|
5926
|
+
import { tool as tool3 } from "ai";
|
|
5911
5927
|
import { z as z5 } from "zod";
|
|
5912
5928
|
import { exec } from "node:child_process";
|
|
5913
5929
|
function createBashTool(ctx) {
|
|
5914
|
-
return
|
|
5930
|
+
return tool3({
|
|
5915
5931
|
description: "Execute a shell command in the workspace directory. Returns stdout, stderr, and exit code.",
|
|
5916
5932
|
inputSchema: z5.object({
|
|
5917
5933
|
command: z5.string().describe("The shell command to execute"),
|
|
@@ -5940,12 +5956,12 @@ function createBashTool(ctx) {
|
|
|
5940
5956
|
}
|
|
5941
5957
|
|
|
5942
5958
|
// opencode/tools/read.ts
|
|
5943
|
-
import { tool as
|
|
5959
|
+
import { tool as tool4 } from "ai";
|
|
5944
5960
|
import { z as z6 } from "zod";
|
|
5945
5961
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
5946
5962
|
import { resolve as resolve4 } from "node:path";
|
|
5947
5963
|
function createReadTool(ctx) {
|
|
5948
|
-
return
|
|
5964
|
+
return tool4({
|
|
5949
5965
|
description: "Read file contents. Supports offset and limit for reading specific line ranges.",
|
|
5950
5966
|
inputSchema: z6.object({
|
|
5951
5967
|
path: z6.string().describe("File path relative to workspace"),
|
|
@@ -5982,12 +5998,12 @@ function createReadTool(ctx) {
|
|
|
5982
5998
|
}
|
|
5983
5999
|
|
|
5984
6000
|
// opencode/tools/write.ts
|
|
5985
|
-
import { tool as
|
|
6001
|
+
import { tool as tool5 } from "ai";
|
|
5986
6002
|
import { z as z7 } from "zod";
|
|
5987
6003
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
5988
6004
|
import { resolve as resolve5, dirname as dirname2 } from "node:path";
|
|
5989
6005
|
function createWriteTool(ctx) {
|
|
5990
|
-
return
|
|
6006
|
+
return tool5({
|
|
5991
6007
|
description: "Create or overwrite a file. Parent directories are created automatically.",
|
|
5992
6008
|
inputSchema: z7.object({
|
|
5993
6009
|
path: z7.string().describe("File path relative to workspace"),
|
|
@@ -6006,12 +6022,12 @@ function createWriteTool(ctx) {
|
|
|
6006
6022
|
}
|
|
6007
6023
|
|
|
6008
6024
|
// opencode/tools/edit.ts
|
|
6009
|
-
import { tool as
|
|
6025
|
+
import { tool as tool6 } from "ai";
|
|
6010
6026
|
import { z as z8 } from "zod";
|
|
6011
6027
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
6012
6028
|
import { resolve as resolve6 } from "node:path";
|
|
6013
6029
|
function createEditTool(ctx) {
|
|
6014
|
-
return
|
|
6030
|
+
return tool6({
|
|
6015
6031
|
description: "Perform exact string replacements in a file. If oldString appears multiple times, provide more surrounding context.",
|
|
6016
6032
|
inputSchema: z8.object({
|
|
6017
6033
|
path: z8.string().describe("File path relative to workspace"),
|
|
@@ -6050,13 +6066,13 @@ function createEditTool(ctx) {
|
|
|
6050
6066
|
}
|
|
6051
6067
|
|
|
6052
6068
|
// opencode/tools/grep.ts
|
|
6053
|
-
import { tool as
|
|
6069
|
+
import { tool as tool7 } from "ai";
|
|
6054
6070
|
import { z as z9 } from "zod";
|
|
6055
6071
|
import { execSync as execSync2 } from "node:child_process";
|
|
6056
6072
|
import { resolve as resolve7 } from "node:path";
|
|
6057
6073
|
import { existsSync as existsSync3 } from "node:fs";
|
|
6058
6074
|
function createGrepTool(ctx) {
|
|
6059
|
-
return
|
|
6075
|
+
return tool7({
|
|
6060
6076
|
description: "Search file contents using regex. Supports file type filtering and context lines.",
|
|
6061
6077
|
inputSchema: z9.object({
|
|
6062
6078
|
pattern: z9.string().describe("The regex pattern to search for"),
|
|
@@ -6088,12 +6104,12 @@ function createGrepTool(ctx) {
|
|
|
6088
6104
|
}
|
|
6089
6105
|
|
|
6090
6106
|
// opencode/tools/glob.ts
|
|
6091
|
-
import { tool as
|
|
6107
|
+
import { tool as tool8 } from "ai";
|
|
6092
6108
|
import { z as z10 } from "zod";
|
|
6093
6109
|
import { execSync as execSync3 } from "node:child_process";
|
|
6094
6110
|
import { resolve as resolve8 } from "node:path";
|
|
6095
6111
|
function createGlobTool(ctx) {
|
|
6096
|
-
return
|
|
6112
|
+
return tool8({
|
|
6097
6113
|
description: "Find files matching a glob pattern.",
|
|
6098
6114
|
inputSchema: z10.object({
|
|
6099
6115
|
pattern: z10.string().describe('Glob pattern (e.g. "src/**/*.ts")'),
|
|
@@ -6116,10 +6132,10 @@ function createGlobTool(ctx) {
|
|
|
6116
6132
|
}
|
|
6117
6133
|
|
|
6118
6134
|
// opencode/tools/web.ts
|
|
6119
|
-
import { tool as
|
|
6135
|
+
import { tool as tool9 } from "ai";
|
|
6120
6136
|
import { z as z11 } from "zod";
|
|
6121
6137
|
function createWebTool(ctx) {
|
|
6122
|
-
return
|
|
6138
|
+
return tool9({
|
|
6123
6139
|
description: "Fetch a URL and return the content as text.",
|
|
6124
6140
|
inputSchema: z11.object({
|
|
6125
6141
|
url: z11.string().describe("The URL to fetch")
|
|
@@ -6144,10 +6160,10 @@ function createWebTool(ctx) {
|
|
|
6144
6160
|
}
|
|
6145
6161
|
|
|
6146
6162
|
// opencode/tools/question.ts
|
|
6147
|
-
import { tool as
|
|
6163
|
+
import { tool as tool10 } from "ai";
|
|
6148
6164
|
import { z as z12 } from "zod";
|
|
6149
6165
|
function createQuestionTool(ctx) {
|
|
6150
|
-
return
|
|
6166
|
+
return tool10({
|
|
6151
6167
|
description: "Ask the user a question when you need more information to proceed.",
|
|
6152
6168
|
inputSchema: z12.object({
|
|
6153
6169
|
question: z12.string().describe("The question to ask the user"),
|
|
@@ -6175,7 +6191,7 @@ function createQuestionTool(ctx) {
|
|
|
6175
6191
|
}
|
|
6176
6192
|
|
|
6177
6193
|
// opencode/tools/skill.ts
|
|
6178
|
-
import { tool as
|
|
6194
|
+
import { tool as tool11 } from "ai";
|
|
6179
6195
|
import { z as z13 } from "zod";
|
|
6180
6196
|
function createSkillTool(ctx) {
|
|
6181
6197
|
const skills = ctx.skillsRegistry.list();
|
|
@@ -6190,7 +6206,7 @@ function createSkillTool(ctx) {
|
|
|
6190
6206
|
<available_skills>
|
|
6191
6207
|
${availableList}
|
|
6192
6208
|
</available_skills>` : "No skills available.";
|
|
6193
|
-
return
|
|
6209
|
+
return tool11({
|
|
6194
6210
|
description,
|
|
6195
6211
|
inputSchema: z13.object({
|
|
6196
6212
|
name: z13.string().describe("The name of the skill to load")
|
|
@@ -6536,7 +6552,7 @@ async function opencode(options) {
|
|
|
6536
6552
|
const modelName = options.model || "deepseek-v4-flash";
|
|
6537
6553
|
const [discoveredSkills] = await Promise.all([discoverSkills(workspace)]);
|
|
6538
6554
|
const skillsRegistry = buildSkillRegistry(discoveredSkills, manualSkills);
|
|
6539
|
-
const provider =
|
|
6555
|
+
const provider = createOpenAI3({ baseURL, apiKey });
|
|
6540
6556
|
const model = provider.chat(modelName);
|
|
6541
6557
|
const pendingQuestions = /* @__PURE__ */ new Map();
|
|
6542
6558
|
const base = new PgModule(pg);
|
|
@@ -7412,6 +7428,10 @@ function createWsHandler(deps) {
|
|
|
7412
7428
|
if (workerId) deps.unregisterRemoteTrigger(workerId, msg.function_id);
|
|
7413
7429
|
break;
|
|
7414
7430
|
}
|
|
7431
|
+
case "invoke": {
|
|
7432
|
+
deps.handleInvoke(ws, msg.invocation_id, msg.function_id, msg.payload);
|
|
7433
|
+
break;
|
|
7434
|
+
}
|
|
7415
7435
|
case "invoke_result": {
|
|
7416
7436
|
deps.handleInvokeResult(msg.invocation_id, msg.result);
|
|
7417
7437
|
break;
|
|
@@ -7651,6 +7671,23 @@ function iii(opts = {}) {
|
|
|
7651
7671
|
p.reject(new Error(error));
|
|
7652
7672
|
pending.delete(invocationId);
|
|
7653
7673
|
}
|
|
7674
|
+
},
|
|
7675
|
+
handleInvoke(ws, invocationId, functionId, payload) {
|
|
7676
|
+
const fn = functions.get(functionId);
|
|
7677
|
+
if (!fn) {
|
|
7678
|
+
ws.send(JSON.stringify({
|
|
7679
|
+
type: "invoke_error",
|
|
7680
|
+
invocation_id: invocationId,
|
|
7681
|
+
error: `Function "${functionId}" not found`
|
|
7682
|
+
}));
|
|
7683
|
+
return;
|
|
7684
|
+
}
|
|
7685
|
+
const ctx = { engine: module, functionId, workerName: fn.workerName };
|
|
7686
|
+
Promise.resolve(fn.handler(payload, ctx)).then((result) => {
|
|
7687
|
+
ws.send(JSON.stringify({ type: "invoke_result", invocation_id: invocationId, result }));
|
|
7688
|
+
}).catch((err) => {
|
|
7689
|
+
ws.send(JSON.stringify({ type: "invoke_error", invocation_id: invocationId, error: err.message }));
|
|
7690
|
+
});
|
|
7654
7691
|
}
|
|
7655
7692
|
});
|
|
7656
7693
|
function doTrigger(request) {
|
|
@@ -7955,19 +7992,24 @@ function registerWorker(url) {
|
|
|
7955
7992
|
export {
|
|
7956
7993
|
Router,
|
|
7957
7994
|
TsxContext,
|
|
7958
|
-
createWorker as Worker,
|
|
7959
7995
|
agent,
|
|
7960
7996
|
aiStream,
|
|
7961
7997
|
auth,
|
|
7962
7998
|
compress,
|
|
7963
7999
|
cors,
|
|
8000
|
+
createOpenAI,
|
|
7964
8001
|
createSSEStream,
|
|
7965
8002
|
createTestServer,
|
|
8003
|
+
createWorker,
|
|
7966
8004
|
defineConfig,
|
|
7967
8005
|
deleteCookie,
|
|
7968
8006
|
deploy,
|
|
8007
|
+
embed,
|
|
8008
|
+
embedMany,
|
|
7969
8009
|
formatSSE,
|
|
7970
8010
|
formatSSEData,
|
|
8011
|
+
generateObject,
|
|
8012
|
+
generateText2 as generateText,
|
|
7971
8013
|
getCookies,
|
|
7972
8014
|
graphql,
|
|
7973
8015
|
health,
|
|
@@ -7979,6 +8021,7 @@ export {
|
|
|
7979
8021
|
logger,
|
|
7980
8022
|
mailer,
|
|
7981
8023
|
messager,
|
|
8024
|
+
openai,
|
|
7982
8025
|
opencode,
|
|
7983
8026
|
postgres,
|
|
7984
8027
|
queue,
|
|
@@ -7993,7 +8036,11 @@ export {
|
|
|
7993
8036
|
serve,
|
|
7994
8037
|
serveStatic,
|
|
7995
8038
|
setCookie,
|
|
8039
|
+
smoothStream,
|
|
8040
|
+
streamObject,
|
|
8041
|
+
streamText,
|
|
7996
8042
|
tenant,
|
|
8043
|
+
tool2 as tool,
|
|
7997
8044
|
tsx,
|
|
7998
8045
|
upload,
|
|
7999
8046
|
useTsx,
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weifuwu",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"description": "Web-standard HTTP framework for Node.js — (req, ctx) => Response",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"dist/",
|
|
16
|
-
"docs/",
|
|
17
16
|
"cli.ts",
|
|
18
17
|
"README.md",
|
|
19
18
|
"LICENSE"
|
package/README.zh.md
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: weifuwu
|
|
3
|
-
description: 面向 Node.js 的 Web 标准 HTTP 框架 — (req, ctx) => Response
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# weifuwu
|
|
7
|
-
|
|
8
|
-
**面向 Node.js 的 Web 标准 HTTP 框架。** `(req, ctx) => Response` — 没有框架特有的对象,只有浏览器原生支持的 Web API。
|
|
9
|
-
|
|
10
|
-
### 设计理念
|
|
11
|
-
|
|
12
|
-
weifuwu 不发明自己的请求/响应抽象。`Request` 和 `Response` 就是你在 `fetch()` 中使用的那套 API——你在浏览器中学到的知识可以直接用在服务端。`ctx` 是唯一的框架对象,它只携带路由解析的结果(`params`、`query`)。
|
|
13
|
-
|
|
14
|
-
所有功能遵循相同的 `(req, ctx) => Response` 约定。Router 负责 HTTP 路由和 WebSocket。其他所有功能——认证、校验、数据库、GraphQL、AI——都是独立的模块,通过 `app.use()` 挂载。
|
|
15
|
-
|
|
16
|
-
## 功能特性
|
|
17
|
-
|
|
18
|
-
- **Web 标准** — `Request` / `Response` / `ReadableStream`,零抽象
|
|
19
|
-
- **零构建** — Node.js v24+ 原生 TypeScript,核心零依赖
|
|
20
|
-
- **Trie 路由器** — 静态 > 参数 > 通配符,子路由挂载,WebSocket
|
|
21
|
-
- **中间件** — 全局/路径级/路由级 — 洋葱模型,支持短路
|
|
22
|
-
- **模块** — 认证、校验、上传、压缩、限流、Cookie、静态文件、CORS、日志
|
|
23
|
-
- **React SSR** — `tsx()` — 页面、布局、数据加载、路由处理、Tailwind CSS、HMR
|
|
24
|
-
- **PostgreSQL** — 类型安全 DDL + CRUD 的 schema 构建器,事务,向量搜索
|
|
25
|
-
- **认证** — 密码 + JWT + OAuth2 服务端(authorization code / PKCE / client_credentials)
|
|
26
|
-
- **实时** — WebSocket,带 agent 路由的消息频道
|
|
27
|
-
- **AI** — 流式端点,DAG 工作流工具,支持 RAG 和工具调用的 AI agent
|
|
28
|
-
- **数据** — Redis 客户端,支持 cron 调度的任务队列
|
|
29
|
-
- **多租户 BaaS** — 动态表,自动 REST + GraphQL,行级隔离
|
|
30
|
-
- **部署** — 自托管 PaaS:多应用代理,零停机更新,自动 SSL
|
|
31
|
-
- **国际化** — 语言检测,JSON 翻译,`ctx.t()`
|
|
32
|
-
- **邮件** — SMTP 或自定义传输
|
|
33
|
-
- **健康检查** — 可配置的 `/health` 端点
|
|
34
|
-
- **环境变量** — `loadEnv()` — 将 `.env` 文件加载到 `process.env`
|
|
35
|
-
- **测试工具** — `createTestServer()` — 一行代码搭建测试服务
|
|
36
|
-
|
|
37
|
-
## 快速开始
|
|
38
|
-
|
|
39
|
-
### Hello World
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
import { serve } from 'weifuwu'
|
|
43
|
-
serve((req, ctx) => new Response('Hello, World!'), { port: 3000 })
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### 完整应用
|
|
47
|
-
|
|
48
|
-
```ts
|
|
49
|
-
import { serve, Router, postgres, user, aiStream, graphql } from 'weifuwu'
|
|
50
|
-
import { openai } from '@ai-sdk/openai'
|
|
51
|
-
|
|
52
|
-
const app = new Router()
|
|
53
|
-
const pg = postgres()
|
|
54
|
-
|
|
55
|
-
// 认证
|
|
56
|
-
const auth = user({ pg, jwtSecret: process.env.JWT_SECRET! })
|
|
57
|
-
await auth.migrate()
|
|
58
|
-
app.use('/auth', auth.router())
|
|
59
|
-
|
|
60
|
-
// AI 流式
|
|
61
|
-
const chat = await aiStream(async (req) => ({
|
|
62
|
-
model: openai('gpt-4o'),
|
|
63
|
-
messages: (await req.json()).messages,
|
|
64
|
-
}))
|
|
65
|
-
app.use('/chat', chat.router())
|
|
66
|
-
|
|
67
|
-
// GraphQL
|
|
68
|
-
const gql = graphql(() => ({
|
|
69
|
-
schema: `type Query { hello: String }`,
|
|
70
|
-
resolvers: { Query: { hello: () => 'world' } },
|
|
71
|
-
}))
|
|
72
|
-
app.use('/graphql', gql.router())
|
|
73
|
-
|
|
74
|
-
// 静态文件
|
|
75
|
-
app.get('/static/*', serveStatic('./public'))
|
|
76
|
-
|
|
77
|
-
serve(app.handler(), { port: 3000 })
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
node app.ts
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## 文档
|
|
85
|
-
|
|
86
|
-
| 模块 | 文档 | 说明 |
|
|
87
|
-
|--------|------|------|
|
|
88
|
-
| **Router** | [README.md](#router) | 路由、中间件、WebSocket、错误处理 |
|
|
89
|
-
| **Middleware** | [README.md](#middleware) | auth, cors, logger, rateLimit, compress, validate, upload, cookie, static |
|
|
90
|
-
| **PostgreSQL** | [README.md](#postgresql) | Schema 构建器、CRUD、DDL、事务、PgModule |
|
|
91
|
-
| **Auth & User** | [README.md](#auth--user) | 密码、JWT、OAuth2 服务端、社交登录示例 |
|
|
92
|
-
| **React SSR** | [README.md](#react-ssr-with-tsx) | 页面、布局、数据加载、Tailwind、shadcn/ui |
|
|
93
|
-
| **AI** | [README.md](#ai-streaming--workflow) | `aiStream()`, `runWorkflow()` |
|
|
94
|
-
| **AI Agent** | [README.md](#ai-agent) | 对话、工具调用、RAG 知识库 |
|
|
95
|
-
| **Opencode** | [README.md](#opencode) | 编程助手、技能、会话、权限 |
|
|
96
|
-
| **Messager** | [README.md](#messager) | 实时聊天、频道、WebSocket、agent 路由 |
|
|
97
|
-
| **GraphQL** | [README.md](#graphql) | 带 GraphiQL 的 GraphQL 端点 |
|
|
98
|
-
| **Tenant BaaS** | [README.md](#tenant-baas) | 动态表、自动 REST + GraphQL、行隔离 |
|
|
99
|
-
| **LogDB** | [README.md](#logdb--structured-event-logging) | 结构化事件日志、分区、元数据搜索 |
|
|
100
|
-
| **Extra** | [README.md](#health-i18n-email--testing) | 健康检查、国际化、邮件、测试工具 |
|
|
101
|
-
|
|
102
|
-
### 基础设施
|
|
103
|
-
|
|
104
|
-
| 模块 | 导入 | 功能 |
|
|
105
|
-
|--------|--------|-----------|
|
|
106
|
-
| PostgreSQL | `postgres(options?)` | 连接池 + schema 构建器 + CRUD + 事务 |
|
|
107
|
-
| Redis | `redis(options?)` | ioredis 客户端,注入为 `ctx.redis` |
|
|
108
|
-
| 队列 | `queue(options?)` | 基于 Redis 的任务队列,支持 cron 调度 |
|
|
109
|
-
| 部署 | `deploy(config)` | 自托管 PaaS,详见 [deploy.md](./deploy.md) |
|
|
110
|
-
|
|
111
|
-
### 可挂载模块
|
|
112
|
-
|
|
113
|
-
所有模块遵循相同模式 — `const m = module(options)` → `app.use('/path', m.router())`:
|
|
114
|
-
|
|
115
|
-
| 模块 | 用途 | 额外提供 |
|
|
116
|
-
|--------|---------|---------------|
|
|
117
|
-
| `user(options)` | 认证(密码 + JWT + OAuth2) | `migrate()`, `middleware()`, `register()`, `login()`, `verify()`, `close()` |
|
|
118
|
-
| `tenant(options)` | 多租户 BaaS | `migrate()`, `middleware()`, `graphql()`, `close()` |
|
|
119
|
-
| `agent(options)` | AI agents | `migrate()`, `run()`, `addKnowledge()`, `close()` |
|
|
120
|
-
| `opencode(options)` | 编程助手 | `migrate()`, `wsHandler()`, `close()` |
|
|
121
|
-
| `messager(options)` | 实时消息 | `migrate()`, `wsHandler()`, `send()`, `close()` |
|
|
122
|
-
| `aiStream(handler)` | AI 流式端点 | — |
|
|
123
|
-
| `graphql(handler)` | GraphQL 端点 | — |
|
|
124
|
-
| `health(options?)` | 健康检查 | — |
|
|
125
|
-
| `iii(options?)` | Worker/Function/Trigger 服务范式 | `migrate()`, `trigger()`, `addWorker()`, `listWorkers()`, `listFunctions()`, `listTriggers()`, `shutdown()` |
|
|
126
|
-
| `registerWorker(url)` | 纯 WebSocket SDK(浏览器/Node) | `registerFunction()`, `registerTrigger()`, `trigger()`, `shutdown()` |
|
|
127
|
-
|
|
128
|
-
### 中间件(全部为 `(req, ctx, next) => Response`)
|
|
129
|
-
|
|
130
|
-
| 中间件 | 说明 |
|
|
131
|
-
|-----------|-------------|
|
|
132
|
-
| `auth(options)` | Bearer token / 自定义请求头 / 验证 / 代理 |
|
|
133
|
-
| `cors(options?)` | CORS,含预检请求、来源白名单、凭据 |
|
|
134
|
-
| `logger(options?)` | 带耗时的请求日志 |
|
|
135
|
-
| `rateLimit(options?)` | 基于内存的限流,带响应头 |
|
|
136
|
-
| `compress(options?)` | Brotli / Gzip / Deflate 压缩 |
|
|
137
|
-
| `validate(schemas)` | Zod 校验(body, query, params) |
|
|
138
|
-
| `upload(options?)` | 多部分文件上传 |
|
|
139
|
-
| `i18n(options)` | 国际化 — `ctx.t()`,语言检测 |
|
|
140
|
-
|
|
141
|
-
### 工具函数
|
|
142
|
-
|
|
143
|
-
| 函数 | 说明 |
|
|
144
|
-
|----------|-------------|
|
|
145
|
-
| `serveStatic(root, options?)` | 静态文件服务 |
|
|
146
|
-
| `loadEnv(path?)` | 加载 `.env` 文件到 `process.env` — 不覆盖、支持注释和引号 |
|
|
147
|
-
| `getCookies(req)` / `setCookie(res, ...)` / `deleteCookie(res, ...)` | Cookie 助手 |
|
|
148
|
-
| `mailer(options)` | 邮件发送(SMTP 或自定义) |
|
|
149
|
-
| `createTestServer(handler)` | 启动测试服务 → `{ server, url }` |
|
|
150
|
-
| `runWorkflow(options)` | 作为 AI SDK `Tool` 的 DAG 执行引擎 |
|
|
151
|
-
| `pgTable(name, columns)` | 类型安全的表 schema 构建器 |
|
|
152
|
-
| `pg.table(name, columns)` | 预绑定表(无需传 `sql` 参数) |
|
|
153
|
-
| `serial()`, `uuid()`, `text()`, ... | 列类型构建器 |
|
|
154
|
-
| `PgModule` | 数据库模块的基类 |
|
|
155
|
-
|
|
156
|
-
## 许可证
|
|
157
|
-
|
|
158
|
-
MIT
|