背景
基于对 Airtable Interface Designer、Salesforce 2GP Package、Power Platform Solution、Retool/Appsmith 等平台的分析(参见 #823),我们确定:
发布粒度应该是 Package(解决方案),而不是单个元数据项。
为什么不是按单个元数据发布?
元数据之间有依赖关系:Interface → View → Object → Field → Flow。如果只发布了 Interface 但没发布它引用的新 Object 字段,终端用户看到的界面会报错。
Salesforce 和 Power Platform 的做法是:整个 Package/Solution 一起发布,要么全部上线,要么全部不上。
行业对标
| 平台 |
发布粒度 |
机制 |
| Salesforce 2GP |
Package(整包) |
sfdx force:package:version:create |
| Power Platform |
Solution(整包) |
Export Managed Solution → Import |
| Retool |
单个 App |
Versioned release |
| Airtable |
单个 Interface |
Publish button |
ObjectStack 定位企业级 → 采用 Salesforce/Power Platform 的 Package 级发布。
现有基础设施
已有的组件已经覆盖了 80%:
| 现有组件 |
作用 |
状态 |
MetadataRecordSchema (metadata-persistence.zod.ts) |
存储所有元数据,含 state: draft/active/archived |
✅ 已有 |
MetadataStateSchema |
生命周期状态枚举 |
✅ 已有 |
IMetadataService (metadata-service.ts) |
CRUD 合约 |
✅ 已有 |
MetadataManager |
实现类 |
✅ 已有 |
DeployBundleSchema (deploy-bundle.zod.ts) |
批量部署 |
✅ 已有 |
InstalledPackageSchema (package-registry.zod.ts) |
包安装状态 |
✅ 已有 |
package_id 字段 on sys_metadata |
元数据归属包 |
✅ 已有 |
需要做的事情
A. 协议层 (packages/spec)
A1. 扩展 MetadataRecordSchema — 加 3 个字段
// packages/spec/src/system/metadata-persistence.zod.ts
// 在现有 MetadataRecordSchema 上补充:
version: z.number().int().min(0).default(0)
.describe('Version counter, increments on each package publish'),
published_definition: z.unknown().optional()
.describe('Snapshot of the last published definition'),
published_at: z.string().datetime().optional()
.describe('When this metadata was last published'),
published_by: z.string().optional()
.describe('Who published this version'),
A2. 扩展 IMetadataService 合约 — 加 3 个方法
// packages/spec/src/contracts/metadata-service.ts
/**
* Publish an entire package:
* 1. Validate all draft items (dependency check)
* 2. Snapshot all items in the package
* 3. Increment package version
* 4. Set all items state → active
*/
publishPackage(packageId: string, options?: {
changeNote?: string;
publishedBy?: string;
validate?: boolean;
}): Promise<PackagePublishResult>;
/**
* Revert entire package to last published state
*/
revertPackage(packageId: string): Promise<void>;
/**
* Get the published version of any metadata item (for runtime serving)
* Returns published_definition if exists, else current definition
*/
getPublished(type: string, name: string): Promise<unknown | undefined>;
A3. 定义 PackagePublishResult 类型
// packages/spec/src/system/metadata-persistence.zod.ts 或新文件
export const PackagePublishResultSchema = z.object({
success: z.boolean(),
packageId: z.string(),
version: z.number().int(),
publishedAt: z.string().datetime(),
itemsPublished: z.number().int(),
validationErrors: z.array(z.object({
type: z.string(),
name: z.string(),
message: z.string(),
})).optional(),
});
B. Runtime 层 (packages/metadata)
B1. MetadataManager 实现 publishPackage()
- 查询
package_id === packageId 的所有元数据
- 依赖校验:检查新引用的 View/Object 是否在同一个包或已发布的包中
- Schema 校验:所有 draft 元数据格式合法
- 快照:
published_definition = clone(definition) for each item
- 版本号递增
- 状态:所有
draft → active
B2. MetadataManager 实现 revertPackage()
- 查询包下所有元数据
definition = clone(published_definition) for each item
state → active(丢弃未发布修改)
B3. MetadataManager 实现 getPublished()
- 返回
published_definition if exists
- Falls back to
definition if never published
C. API 层 (packages/runtime)
C1. HttpDispatcher 加 publish 端点
POST /api/v1/packages/:packageId/publish → publishPackage()
POST /api/v1/packages/:packageId/revert → revertPackage()
GET /api/v1/metadata/:type/:name/published → getPublished()
D. 测试
E. 文档 & ROADMAP
数据流
设计者日常编辑(自动保存到 draft)
├── 改了 Object "opportunity" 加了个字段
├── 改了 View "opp_list" 显示新字段
├── 改了 Interface "sales_dashboard" 布局
│
▼ 所有改动都在 draft,终端用户看不到
│
▼ [点击 Publish Package "crm"]
│
├── 1. 依赖校验:新 Interface 引用的 View/Object 都在这个包里吗?✓
├── 2. Schema 校验:所有 draft 元数据格式合法吗?✓
├── 3. 快照:crm 包下所有元数据 → published_definition = clone(definition)
├── 4. 版本号:crm package version++
├── 5. 状态:所有 draft → active
│
▼ 终端用户立刻看到所有变更,且保证一致性
工作量估计
| 改动 |
工作量 |
MetadataRecordSchema 加 4 个字段 |
1 天 |
IMetadataService 加 3 个方法 + 类型定义 |
1 天 |
MetadataManager 实现 3 个方法(含依赖校验) |
3 天 |
HttpDispatcher 加 3 个端点 |
1 天 |
| 测试 |
2 天 |
| 文档 |
1 天 |
| 总计 |
~1.5 周 |
关联
验收标准
背景
基于对 Airtable Interface Designer、Salesforce 2GP Package、Power Platform Solution、Retool/Appsmith 等平台的分析(参见 #823),我们确定:
发布粒度应该是 Package(解决方案),而不是单个元数据项。
为什么不是按单个元数据发布?
元数据之间有依赖关系:Interface → View → Object → Field → Flow。如果只发布了 Interface 但没发布它引用的新 Object 字段,终端用户看到的界面会报错。
Salesforce 和 Power Platform 的做法是:整个 Package/Solution 一起发布,要么全部上线,要么全部不上。
行业对标
sfdx force:package:version:createObjectStack 定位企业级 → 采用 Salesforce/Power Platform 的 Package 级发布。
现有基础设施
已有的组件已经覆盖了 80%:
MetadataRecordSchema(metadata-persistence.zod.ts)state: draft/active/archivedMetadataStateSchemaIMetadataService(metadata-service.ts)MetadataManagerDeployBundleSchema(deploy-bundle.zod.ts)InstalledPackageSchema(package-registry.zod.ts)package_id字段 onsys_metadata需要做的事情
A. 协议层 (
packages/spec)A1. 扩展
MetadataRecordSchema— 加 3 个字段A2. 扩展
IMetadataService合约 — 加 3 个方法A3. 定义
PackagePublishResult类型B. Runtime 层 (
packages/metadata)B1.
MetadataManager实现publishPackage()package_id === packageId的所有元数据published_definition = clone(definition)for each itemdraft→activeB2.
MetadataManager实现revertPackage()definition = clone(published_definition)for each itemstate → active(丢弃未发布修改)B3.
MetadataManager实现getPublished()published_definitionif existsdefinitionif never publishedC. API 层 (
packages/runtime)C1.
HttpDispatcher加 publish 端点D. 测试
publishPackage()单元测试:正常发布、依赖校验失败、空包revertPackage()单元测试:正常回滚、从未发布过时报错getPublished()单元测试:已发布返回快照、未发布返回 definitionE. 文档 & ROADMAP
packages/metadata/README.md加 Package Publish 章节ROADMAP.mdcontent/docs/guides/contracts/metadata-service.mdx加 publishPackage 文档数据流
工作量估计
MetadataRecordSchema加 4 个字段IMetadataService加 3 个方法 + 类型定义MetadataManager实现 3 个方法(含依赖校验)HttpDispatcher加 3 个端点关联
objectstack-ai/objectui(cross-repo)IDeployPipelineService— deploy = 初始化时批量 publishpackage-upgrade.zod.ts— upgrade = 外部包发布后 import验收标准
pnpm test全部通过.describe(),published_*字段是.optional()保证兼容MetadataManager.publishPackage()可正确快照+版本递增+状态转换MetadataManager.getPublished()返回已发布快照,未发布返回当前 definition