Skip to content

[协议+Runtime] Package 级统一元数据发布机制 (publishPackage) #824

@hotlong

Description

@hotlong

背景

基于对 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
  • 版本号递增
  • 状态:所有 draftactive

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. 测试

  • publishPackage() 单元测试:正常发布、依赖校验失败、空包
  • revertPackage() 单元测试:正常回滚、从未发布过时报错
  • getPublished() 单元测试:已发布返回快照、未发布返回 definition
  • 集成测试:编辑 → 发布 → 编辑 → 回滚 → 终端用户看到的版本正确

E. 文档 & ROADMAP

  • 更新 packages/metadata/README.md 加 Package Publish 章节
  • 更新 ROADMAP.md
  • 更新 content/docs/guides/contracts/metadata-service.mdx 加 publishPackage 文档

数据流

设计者日常编辑(自动保存到 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 周

关联

验收标准

  • pnpm test 全部通过
  • 协议变更都有 .describe()published_* 字段是 .optional() 保证兼容
  • MetadataManager.publishPackage() 可正确快照+版本递增+状态转换
  • MetadataManager.getPublished() 返回已发布快照,未发布返回当前 definition
  • REST 端点可用
  • ROADMAP.md 更新

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions