Repository navigation
Description
背景
源自 #2023 的排查:示例应用从根 @objectstack/spec 导入类型 → 解析成 any → 静默吞掉所有对象字面量的类型检查,掩盖了约 30 个真实类型错误。根因之一是这些域没有一个统一、类型安全的编写入口,作者(尤其是 AI)只能写裸 : X 字面量,而输出态(z.infer)会把 .default() 字段变必填、拒绝 CEL 字符串简写。
后续的 #2026 / #2029 已经堵上了 CI 闸门(示例 typecheck + ESLint 导入守卫)。本 issue 处理更上游的一致性问题。
问题:三种并存的编写惯用法
| 写法 | 适用域 | 输入态人体工学 | 编写处类型安全 | 运行期校验 |
|---|---|---|---|---|
defineX() 工厂 |
view/flow/job/agent/app/portal/dataset/book… (19 个) | ✅ | ✅ | ✅ (.parse()) |
ObjectSchema.create() / Field.x() 构建器 |
object / field | ✅ | ✅ | ✅ |
裸类型字面量 : X |
下列 16 个域 | ❌ | ❌ |
用哪种纯粹看历史,无规律。第三类正是 #2023 翻车的地方。
缺口清单(当前 main 实测)
16 个可编写域没有 defineX 工厂:
Datasource、Connector、Policy、SharingRule、Role、PermissionSet、EmailTemplateDefinition、Report、Webhook、ObjectExtension、Cube、Mapping、Theme、TranslationBundle、Page、Action
其中 6 个连 XInput 别名也没有(需一并补):Policy、Cube、Mapping、Theme、TranslationBundle、Page
(已有 defineX 的 19 个域作为模板:defineView/defineForm/defineApp/defineFlow/defineJob/defineBook/defineAgent/defineTool/defineSkill/definePortal/defineDataset/defineStack/defineSeed/defineSolutionBlueprint/defineViewItem/defineActionDescriptor/defineFlowBuilderConfig/defineObjectDesignerConfig/defineStudioPlugin)
长期价值
- 让 fix(examples): typecheck example apps clean + gate in CI #2023 那一类 bug 在结构上不可能再发生:
defineX是值导入,坏掉会立刻硬报错,没有any可退化躲藏。 - AI 只需学一种写法(对齐"模板都是 AI 写的、避免 AI 犯错"的北极星):消灭"看着对其实错"的输出态字面量选项。
- 编写期错误信息好得多:
.parse()接已导出的错误映射(objectStackErrorMap/safeParsePretty),给出webhook.timeoutMs 必须 ≥ 1000这类就地、字段级提示,而非 TS 结构不匹配的墙。 - 隔离 schema 变动:给某 schema 加
.default()不再静默打破生态里所有裸输出态字面量。
defineX(config: z.input<typeof XSchema>): X { return XSchema.parse(config); } 是唯一同时满足"输入态人体工学 + 编写处类型安全 + 运行期校验"三项的写法;裸 : XInput 只满足前两项。
建议落地方式(两个 PR)
- PR 1 — spec:为上述 16 个域新增
defineX工厂(每个约 3 行,与现有 19 个机械同构),并补齐 6 个缺失的XInput别名。根index.ts按现有约定 re-export。带minorchangeset(@objectstack/spec)。 - PR 2 — 示例迁移:把 example apps(crm/showcase/todo)里相关域改用新工厂,作为参考示范。fix(examples): typecheck example apps clean + gate in CI #2023 的 typecheck + ESLint 闸门兜底。
成本 / 取舍
- 公共 API 面增长(纯增量、零设计风险,照搬现有写法)。
- 流入
defineStack的域会双重 parse —— 开销可忽略,换来更早/就地校验。 - 不动 object/field 的构建器路径(它们已有良好入口)。
TranslationBundle是z.record,defineTranslationBundle形态略特殊但仍成立。
验收标准
- 16 个域均有
defineX工厂并从根 re-export - 6 个缺失的
XInput别名补齐 - 示例应用相关域迁移到工厂写法
-
pnpm --filter './examples/*' typecheck与pnpm lint全绿 -
@objectstack/specchangeset 已添加
Activity
feat(spec): defineX factories + XInput aliases for the 16 remaining w…
test(spec): downstream-consumer contract as a backward-compat gate (#…
ci(spec): public API-surface snapshot gate to catch silent export dri…
ci(release): pre-publish backward-compat smoke against live hotcrm (#…
test(spec): extend downstream-contract to all 16 writable domains (#2035
ci(lint): extend the authoring-entry guard to packages/apps (#2035) (#…
docs(adr): ADR-0059 — third-party backward-compatibility validation g…
ci(spec): signature-level snapshot for defineX factories — catch narr…
fix(example-crm): author SalesByAccountReport via defineReport (lint #…
docs(skills): author the 16 factory domains via defineX, not bare lit…
ci(docs): guard skills/docs against bare metadata literals (#2035) (#…
fix(showcase): drop bare type annotation on defineConnector authoring…
chore(release)!: enter RC pre-mode and anchor the train as the v17 ma…
fix(scripts): check:doc-authoring 覆盖 .claude 语料,worktrees 进 SKIP_PATHS (
fix(vscode): snippets expand to metadata the spec accepts, and a gate…
test(qa): make the backward-compatibility gate's TYPE half read spec …
skills(i18n): author the generated-bundle example through defineTrans…