stitch-sdk-domain-design

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Stitch SDK Domain Design

Stitch SDK 领域设计

This skill teaches you how to perform Stage 2 of the generation pipeline: reading tool schemas and producing
domain-map.json
— the intermediate representation that drives codegen.

本技能将教你如何执行生成流水线的第2阶段:读取工具Schema并生成
domain-map.json
——这是驱动代码生成(codegen)的中间表示。

Your Inputs

你的输入

  1. tools-manifest.json
    — raw MCP tool schemas captured from the server (includes
    outputSchema
    )
  2. ir-schema.ts
    — Zod schema defining valid domain-map structure (the canonical contract)
  3. Existing
    domain-map.json
    — the current IR (if extending, not starting fresh)
  4. The
    stitch-sdk-development
    skill
    — for understanding the pipeline context
  1. tools-manifest.json
    —— 从服务器捕获的原始MCP工具Schema(包含
    outputSchema
  2. ir-schema.ts
    —— 定义有效domain-map结构的Zod Schema(标准契约)
  3. 现有
    domain-map.json
    —— 当前的中间表示(IR)(如果是扩展而非从零开始)
  4. stitch-sdk-development
    技能
    —— 用于理解流水线上下文

Your Output

你的输出

A valid
domain-map.json
with two sections:
classes
and
bindings
, validated by
ir-schema.ts
.
[!IMPORTANT] Your output is validated twice by the codegen: structurally (Zod IR schema) and semantically (projection steps verified against
outputSchema
from the tools-manifest).

一个有效的
domain-map.json
,包含
classes
bindings
两个部分,并通过
ir-schema.ts
验证。
[!IMPORTANT] 你的输出会被代码生成工具两次验证:结构验证(通过Zod IR Schema)和语义验证(投影步骤与tools-manifest中的
outputSchema
进行校验)。

Designing Classes

设计领域类

Each class represents a domain entity. Ask: "What noun does the user interact with?"
json
{
  "Stitch": {
    "description": "Main entry point. Manages projects.",
    "constructorParams": [],
    "isRoot": true,
    "factories": [
      { "method": "project", "returns": "Project", "description": "Create a Project handle from an ID." }
    ]
  }
}
每个类代表一个领域实体。思考:“用户会与哪个名词交互?”
json
{
  "Stitch": {
    "description": "Main entry point. Manages projects.",
    "constructorParams": [],
    "isRoot": true,
    "factories": [
      { "method": "project", "returns": "Project", "description": "Create a Project handle from an ID." }
    ]
  }
}

Key decisions:

关键决策:

FieldPurposeExample
constructorParams
Fields stored on the instance
["projectId", "screenId"]
fieldMapping
Per-field data source mapping with optional
stripPrefix
See below
parentField
Which param is injected from a parent class
"projectId"
idField
Which param the
.id
getter aliases
"screenId"
factories
Local factory methods (no API call)
[{ "method": "project", "returns": "Project" }]
字段用途示例
constructorParams
存储在实例中的字段
["projectId", "screenId"]
fieldMapping
带可选
stripPrefix
的逐字段数据源映射
见下文
parentField
从父类注入的参数
"projectId"
idField
.id
getter所别名的参数
"screenId"
factories
本地工厂方法(无API调用)
[{ "method": "project", "returns": "Project" }]

Field Mapping

字段映射

Use
fieldMapping
when a param needs a different source field, prefix stripping, or a fallback:
json
{
  "constructorParams": ["projectId", "screenId"],
  "fieldMapping": {
    "projectId": { "from": "name", "stripPrefix": "projects/" },
    "screenId": { "from": "id", "fallback": { "field": "name", "splitOn": "/screens/" } }
  }
}
  • stripPrefix
    : Removes a resource name prefix from the value
  • fallback
    : If the primary field is missing, splits an alternate field on a delimiter

当参数需要不同的源字段、前缀去除或回退时,使用
fieldMapping
json
{
  "constructorParams": ["projectId", "screenId"],
  "fieldMapping": {
    "projectId": { "from": "name", "stripPrefix": "projects/" },
    "screenId": { "from": "id", "fallback": { "field": "name", "splitOn": "/screens/" } }
  }
}
  • stripPrefix
    :从值中移除资源名称前缀
  • fallback
    :如果主字段缺失,使用分隔符分割备用字段

Designing Bindings

设计绑定

Each binding maps one MCP tool to one class method. Ask: "Who owns this action?"
每个绑定将一个MCP工具映射到一个类方法。思考:“谁拥有这个操作?”

Arg routing

参数路由

TypeMeaningCode generated
self
From
this.field
projectId: this.projectId
param
From method parameter
prompt: prompt
computed
Template interpolation
name: \
projects/${this.projectId}/screens/${screenId}``
selfArray
Wrap self field as array
selectedScreenIds: [this.screenId]
Optional params use
"optional": true
. Renamed params use
"rename": "newName"
.
类型含义生成的代码
self
来自
this.field
projectId: this.projectId
param
来自方法参数
prompt: prompt
computed
模板插值
name: \
projects/${this.projectId}/screens/${screenId}``
selfArray
将自身字段包装为数组
selectedScreenIds: [this.screenId]
可选参数使用
"optional": true
。重命名参数使用
"rename": "newName"

Response Projections

响应投影

The
returns.projection
array tells codegen how to navigate the API response. Each step is a
ProjectionStep
:
typescript
{ prop: string; index?: number; each?: boolean; fallback?: string }
ProjectionGenerated codeUse when
[]
(empty)
raw
Direct return (whole response)
[{ "prop": "projects" }]
raw.projects
Array inside object
[{ "prop": "outputComponents", "index": 0 }, { "prop": "design" }, { "prop": "screens", "index": 0 }]
raw.outputComponents[0].design.screens[0]
Deeply nested single item
[{ "prop": "outputComponents", "each": true }, { "prop": "design" }, { "prop": "screens", "each": true }]
flatMap
chain
Collect all items across arrays
[{ "prop": "screenshot" }, { "prop": "downloadUrl" }]
raw.screenshot.downloadUrl
Navigate nested properties
Decision: Use
"index": 0
when extracting a single item. Use
"each": true
when collecting all items (array result). You cannot use both on the same step.
[!TIP] Every
prop
in a projection is validated against the tool's
outputSchema
at codegen time. If you typo a property name, codegen will fail with a diagnostic listing the available properties.
returns.projection
数组告诉代码生成工具如何导航API响应。每一步都是一个
ProjectionStep
typescript
{ prop: string; index?: number; each?: boolean; fallback?: string }
投影方式生成的代码使用场景
[]
(空)
raw
直接返回(整个响应)
[{ "prop": "projects" }]
raw.projects
对象内的数组
[{ "prop": "outputComponents", "index": 0 }, { "prop": "design" }, { "prop": "screens", "index": 0 }]
raw.outputComponents[0].design.screens[0]
深层嵌套的单个项
[{ "prop": "outputComponents", "each": true }, { "prop": "design" }, { "prop": "screens", "each": true }]
flatMap
链式调用
收集数组中的所有项
[{ "prop": "screenshot" }, { "prop": "downloadUrl" }]
raw.screenshot.downloadUrl
导航嵌套属性
决策:提取单个项时使用
"index": 0
。收集所有项(数组结果)时使用
"each": true
。你不能在同一步骤中同时使用两者。
[!TIP] 投影中的每个
prop
都会在代码生成时与工具的
outputSchema
进行验证。如果你输入了错误的属性名称,代码生成会失败并显示诊断信息,列出可用的属性。

Return class wrapping

返回类包装

When
returns.class
is set, the extracted data is wrapped in a domain class constructor:
json
{ "returns": { "class": "Screen", "projection": [{ "prop": "screens" }], "array": true } }
The codegen automatically spreads
parentField
into the data if the child class declares one.
当设置
returns.class
时,提取的数据会被包装到领域类的构造函数中:
json
{ "returns": { "class": "Screen", "projection": [{ "prop": "screens" }], "array": true } }
如果子类声明了
parentField
,代码生成工具会自动将其展开到数据中。

Cache-aware methods

缓存感知方法

Add a
cache
field with a structured
projection
to check
this.data
before calling the API:
json
{
  "cache": {
    "projection": [{ "prop": "htmlCode" }, { "prop": "downloadUrl" }],
    "description": "Use cached HTML download URL from generation response if available"
  }
}
When the cached property is a nested object (like
File
with a
downloadUrl
), use multiple projection steps to drill into it.
Generated code:
typescript
if (this.data?.htmlCode?.downloadUrl) return this.data?.htmlCode?.downloadUrl;
// ... else call API

添加带有结构化
projection
cache
字段,以在调用API前检查
this.data
json
{
  "cache": {
    "projection": [{ "prop": "htmlCode" }, { "prop": "downloadUrl" }],
    "description": "Use cached HTML download URL from generation response if available"
  }
}
当缓存的属性是嵌套对象(比如带有
downloadUrl
File
)时,使用多个投影步骤深入其中。
生成的代码:
typescript
if (this.data?.htmlCode?.downloadUrl) return this.data?.htmlCode?.downloadUrl;
// ... else call API

Decision Framework

决策框架

When mapping a new tool, answer these questions:
  1. Which class? Look at which fields the tool requires. If it needs
    projectId
    from
    self
    , it belongs on
    Project
    or
    Screen
    . If it needs nothing from self, it belongs on
    Stitch
    .
  2. Which method name? Use the verb from the tool name, simplified.
    generate_screen_from_text
    generate
    .
    edit_screens
    edit
    .
  3. Arguments from self or param? If the caller already has the data (because they're calling a method on themselves), use
    self
    . If they need to provide it, use
    param
    .
  4. How deep is the return? Check the tool's
    outputSchema
    in
    tools-manifest.json
    . Build the
    projection
    array step-by-step to navigate to the useful data.
  5. Should it cache? If the data is available from a previous response (like generation), add a cache field with the projection path.

映射新工具时,回答以下问题:
  1. 归属哪个类? 查看工具需要哪些字段。如果它需要来自
    self
    projectId
    ,则属于
    Project
    Screen
    类。如果它不需要来自self的任何数据,则属于
    Stitch
    类。
  2. 方法名称是什么? 使用工具名称中的动词,简化处理。
    generate_screen_from_text
    generate
    edit_screens
    edit
  3. 参数来自self还是param? 如果调用者已经拥有数据(因为他们在调用自身的方法),使用
    self
    。如果需要调用者提供数据,使用
    param
  4. 返回数据的深度如何?查看
    tools-manifest.json
    中的工具
    outputSchema
    。逐步构建
    projection
    数组以导航到有用的数据。
  5. 是否需要缓存? 如果数据可以从之前的响应中获取(比如生成结果),添加带有投影路径的cache字段。

Validation

验证

After editing
domain-map.json
:
bash
bun scripts/generate-sdk.ts     # Validates IR + projections, then generates
npx tsc --noEmit                 # Type check
npx vitest run                   # Unit tests
bun scripts/e2e-test.ts          # E2E tests
bun scripts/validate-generated.ts  # Lock integrity
If a projection is invalid, you'll see:
❌ Binding "Project.generate" projection step 2:
   property "screenz" not found in outputSchema.
   Available properties: screens, components, metadata
编辑
domain-map.json
后:
bash
bun scripts/generate-sdk.ts     # 验证IR和投影,然后生成代码
npx tsc --noEmit                 # 类型检查
npx vitest run                   # 单元测试
bun scripts/e2e-test.ts          # 端到端测试
bun scripts/validate-generated.ts  # 锁定完整性
如果投影无效,你会看到:
❌ Binding "Project.generate" projection step 2:
   property "screenz" not found in outputSchema.
   Available properties: screens, components, metadata