fix: AMD deps not injected — router was pre-codegen'ing, bypassing requires
Root cause: router.ts called generateWorkerCode() before passing to backend,
so backend always received 'code' (mode A) and never entered the requires
branch (mode B). Also stored original execute body in meta for clean dep
resolution instead of fragile regex extraction.
Fix:
- Router passes execute directly to backend, lets backend handle codegen
- Meta now stores execute field for dependency resolution
- resolveDependencies uses meta.execute (falls back to regex extraction)
Verified end-to-end:
- formal-greet(requires greet-name) → '[Formal] Hello, Scott!'
- xiaoju-github-repos(requires xiaoju-github-token) → real GitHub API response
小橘 🍊 (NEKO Team)
This commit is contained in:
+217
@@ -0,0 +1,217 @@
|
||||
# AMD 风格 Capability 组合功能演示
|
||||
|
||||
本演示展示 Sigil 新增的 AMD 风格依赖管理功能。
|
||||
|
||||
## 1. 基础依赖注入
|
||||
|
||||
### 部署基础 capability
|
||||
|
||||
```bash
|
||||
curl -X POST https://sigil.example.com/_api/deploy \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "github-token",
|
||||
"execute": "return \"ghp_example_token\";",
|
||||
"type": "normal",
|
||||
"description": "获取 GitHub API token"
|
||||
}'
|
||||
```
|
||||
|
||||
### 部署依赖该 capability 的服务
|
||||
|
||||
```bash
|
||||
curl -X POST https://sigil.example.com/_api/deploy \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "github-api",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"endpoint": {"type": "string"},
|
||||
"method": {"type": "string", "default": "GET"}
|
||||
},
|
||||
"required": ["endpoint"]
|
||||
},
|
||||
"execute": "const token = await deps[\"github-token\"](); const response = await fetch(`https://api.github.com${input.endpoint}`, { method: input.method, headers: { \"Authorization\": `Bearer ${token}` } }); return response.json();",
|
||||
"type": "normal",
|
||||
"description": "GitHub API 客户端",
|
||||
"requires": ["github-token"]
|
||||
}'
|
||||
```
|
||||
|
||||
## 2. 多依赖组合
|
||||
|
||||
### 部署翻译服务
|
||||
|
||||
```bash
|
||||
curl -X POST https://sigil.example.com/_api/deploy \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "translate",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {"type": "string"},
|
||||
"lang": {"type": "string", "default": "zh"}
|
||||
},
|
||||
"required": ["text"]
|
||||
},
|
||||
"execute": "return `[${input.lang}] ${input.text}`;",
|
||||
"type": "normal"
|
||||
}'
|
||||
```
|
||||
|
||||
### 部署使用多个依赖的服务
|
||||
|
||||
```bash
|
||||
curl -X POST https://sigil.example.com/_api/deploy \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "repo-summary",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"repo": {"type": "string"}
|
||||
},
|
||||
"required": ["repo"]
|
||||
},
|
||||
"execute": "const repoData = await deps[\"github-api\"]({endpoint: `/repos/${input.repo}`}); const summary = await deps[\"translate\"]({text: repoData.description, lang: \"zh\"}); return {name: repoData.name, description_cn: summary, stars: repoData.stargazers_count};",
|
||||
"type": "normal",
|
||||
"description": "获取仓库信息并翻译描述",
|
||||
"requires": ["github-api", "translate"]
|
||||
}'
|
||||
```
|
||||
|
||||
## 3. 链式依赖
|
||||
|
||||
### A depends on B, B depends on C
|
||||
|
||||
```bash
|
||||
# 部署 C (基础服务)
|
||||
curl -X POST https://sigil.example.com/_api/deploy \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "base-logger",
|
||||
"execute": "return `[LOG] ${new Date().toISOString()}`;",
|
||||
"type": "normal"
|
||||
}'
|
||||
|
||||
# 部署 B (中间层,依赖 C)
|
||||
curl -X POST https://sigil.example.com/_api/deploy \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "middleware",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"execute": "const log = await deps[\"base-logger\"](); return `${log} [MIDDLEWARE] ${input.action || \"unknown\"}`;",
|
||||
"type": "normal",
|
||||
"requires": ["base-logger"]
|
||||
}'
|
||||
|
||||
# 部署 A (顶层,依赖 B)
|
||||
curl -X POST https://sigil.example.com/_api/deploy \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "api-handler",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"request": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"execute": "const mid = await deps[\"middleware\"]({action: \"handle_request\"}); return `${mid} [API] Processing: ${input.request}`;",
|
||||
"type": "normal",
|
||||
"requires": ["middleware"]
|
||||
}'
|
||||
```
|
||||
|
||||
## 4. 调用示例
|
||||
|
||||
```bash
|
||||
# 调用 repo-summary (会自动使用其所有依赖)
|
||||
curl "https://sigil.example.com/run/repo-summary?repo=microsoft/vscode"
|
||||
|
||||
# 调用 api-handler (会自动使用链式依赖)
|
||||
curl "https://sigil.example.com/run/api-handler?request=get_user_data"
|
||||
```
|
||||
|
||||
## 5. 生成的代码结构
|
||||
|
||||
当使用 `requires` 时,生成的 Worker 代码包含:
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
async fetch(request) {
|
||||
// ... 输入解析 ...
|
||||
|
||||
// AMD deps - 每个依赖内联为函数
|
||||
const deps = {
|
||||
'github-token': async (params = {}) => {
|
||||
const input = params;
|
||||
return "ghp_example_token";
|
||||
},
|
||||
'translate': async (params = {}) => {
|
||||
const input = {};
|
||||
if (params.text !== undefined) input.text = params.text;
|
||||
if (params.lang !== undefined) input.lang = params.lang;
|
||||
if (input.lang === undefined) input.lang = "zh";
|
||||
return `[${input.lang}] ${input.text}`;
|
||||
}
|
||||
};
|
||||
|
||||
// Execute user function (with deps)
|
||||
const __result = await (async (input, deps) => {
|
||||
const repoData = await deps["github-api"]({endpoint: `/repos/${input.repo}`});
|
||||
const summary = await deps["translate"]({text: repoData.description, lang: "zh"});
|
||||
return {name: repoData.name, description_cn: summary, stars: repoData.stargazers_count};
|
||||
})(input, deps);
|
||||
|
||||
// ... 输出处理 ...
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 6. 循环依赖检测
|
||||
|
||||
尝试创建循环依赖会被自动检测并阻止:
|
||||
|
||||
```bash
|
||||
# 这会失败,因为会创建 A -> A 循环
|
||||
curl -X POST https://sigil.example.com/_api/deploy \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "self-ref",
|
||||
"execute": "const self = await deps[\"self-ref\"](); return self;",
|
||||
"type": "normal",
|
||||
"requires": ["self-ref"]
|
||||
}'
|
||||
```
|
||||
|
||||
错误响应:
|
||||
```json
|
||||
{
|
||||
"error": "Failed to resolve dependencies: Circular dependency detected: self-ref -> self-ref"
|
||||
}
|
||||
```
|
||||
|
||||
## 特性总结
|
||||
|
||||
✅ **依赖声明**: `requires` 字段声明依赖关系
|
||||
✅ **自动 bundle**: 依赖代码自动内联到主 capability
|
||||
✅ **循环检测**: 防止无限递归的循环依赖
|
||||
✅ **参数解析**: 依赖支持 schema 参数验证
|
||||
✅ **向后兼容**: 现有 capability 不受影响
|
||||
✅ **递归解析**: 支持多层依赖链(A->B->C)
|
||||
✅ **类型安全**: TypeScript 严格模式支持
|
||||
@@ -71,18 +71,17 @@ export class WorkerPool implements SigilBackend {
|
||||
Object.assign(deps, nestedDeps)
|
||||
}
|
||||
|
||||
// 对于 schema+execute 模式的依赖,从 code 中提取 execute body
|
||||
// Use stored execute body if available, fall back to extraction from code
|
||||
const executeBody = depMeta.execute || this.extractExecuteBodyFromWorkerCode(depCode)
|
||||
|
||||
if (depMeta.schema) {
|
||||
// 从生成的 worker code 中提取用户的 execute body
|
||||
// 这个比较 hacky,实际应该存储原始的 execute body
|
||||
// 但为了兼容现有代码,我们从生成的代码中反向提取
|
||||
deps[depName] = {
|
||||
code: this.extractExecuteBodyFromWorkerCode(depCode),
|
||||
code: executeBody,
|
||||
schema: depMeta.schema
|
||||
}
|
||||
} else {
|
||||
// 完整代码模式,直接使用
|
||||
deps[depName] = { code: depCode }
|
||||
// Code mode or no schema — use stored execute or full code
|
||||
deps[depName] = { code: depMeta.execute || depCode }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +168,7 @@ export class WorkerPool implements SigilBackend {
|
||||
await this.kv.setCode(capability, finalCode)
|
||||
await this.kv.setMeta(capability, {
|
||||
type, ttl, created_at: now, bindings, description, tags, examples, schema, requires,
|
||||
execute: execute || undefined,
|
||||
})
|
||||
await this.kv.setLru(capability, { last_access: now, access_count: 0, deployed: true })
|
||||
await this.kv.setRoute(capability, { worker_name: capability, subdomain: '' })
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface KvMetaValue {
|
||||
examples?: string[]
|
||||
schema?: InputSchema
|
||||
requires?: string[]
|
||||
execute?: string // original execute body, stored for AMD dependency resolution
|
||||
}
|
||||
|
||||
export interface KvLruValue {
|
||||
|
||||
+5
-2
@@ -85,8 +85,9 @@ async function handleDeploy(request: Request, env: RouterEnv): Promise<Response>
|
||||
return jsonError(400, 'Must specify either code or schema+execute')
|
||||
}
|
||||
|
||||
let code: string
|
||||
let code: string | undefined
|
||||
let schema: InputSchema | undefined
|
||||
let execute: string | undefined
|
||||
|
||||
if (body.code) {
|
||||
code = body.code
|
||||
@@ -95,7 +96,8 @@ async function handleDeploy(request: Request, env: RouterEnv): Promise<Response>
|
||||
return jsonError(400, 'execute is required when using schema mode')
|
||||
}
|
||||
schema = body.schema || { type: 'object', properties: {} }
|
||||
code = generateWorkerCode(schema, body.execute)
|
||||
execute = body.execute
|
||||
// Don't codegen here — let backend handle it (supports AMD requires)
|
||||
}
|
||||
|
||||
// Check deploy cooldown
|
||||
@@ -104,6 +106,7 @@ async function handleDeploy(request: Request, env: RouterEnv): Promise<Response>
|
||||
const result = await env.backend.deploy({
|
||||
name: body.name,
|
||||
code,
|
||||
execute,
|
||||
schema,
|
||||
type: body.type,
|
||||
ttl: body.ttl,
|
||||
|
||||
Reference in New Issue
Block a user