关于 storage-creator
storage-creator 是用于在 Datex Studio 分支上创建或修改存储组件的技能,面向需要保存功能自有数据的开发流程。它涵盖存储结构、命令行验证与提交,并强调通过函数访问存储及评估架构变更影响。
主要功能
- 01创建和修改存储组件
- 02通过 dxs 命令行验证并提交
- 03定义列及其类型和必需性
- 04说明 required 列的读后补丁模式
- 05规划架构演进与影响分析
- 06规范函数层的存储访问
适用场景
- 01保存功能专属配置或规则数据
- 02为存储组件设计列结构
- 03修改已有存储架构
- 04通过函数为界面提供存储数据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ datex/ skills --skill storage-creator安装后在 Claude Code 中输入/storage-creator确认已加载。
附带文件evals/evals.jsonreferences/storage.md
详细介绍
存储创建器
在分支上编写或修改 Datex Studio 存储组件(configurationTypeId=17)——这是由功能拥有、持久化于云端并由 Mongo 提供支持的集合。存储用于保存 Footprint OData 架构之外的状态:配置选项、规则表、快照捕获、派生分析汇总,以及任何属于“功能自身数据”而非一等 WMS 实体的数据。存储本身没有代码字符串;它在函数层通过 $db.<Package>.<storage_referenceName> 运行时全局对象供调用方使用。
参考资料
- ../datex-studio-shared/branch-setup.md — 分支/连接选择(各技能通用)
- references/storage.md — 权威存储编写参考:文件结构、列描述符、
required: true的先读后补丁陷阱、增量演进规则、常见模式 - ../datex-studio-conventions/file-format.md —
configurationTypeId表和编辑规则 - ../datex-studio-conventions/naming-conventions.md —
_storage后缀约定、文件名主体匹配 - ../datex-studio-conventions/defaults.md —
accessModifier默认值 - ../datex-studio-runtime/calling-conventions.md —
$db仅限函数层;操作无法访问存储 - ../db-query/references/db.md —
$db运行时全局对象:访问路径、API 接口、流式谓词 DSL(.equals/.and/.or/.isNull/.in——切勿使用===/&&/||) - ../function-creator/references/functions.md — 函数是唯一可调用
$db的层;写入端代码位于此处 - ../datasource-creator/references/flow-datasources.md — 封装存储读取、供 UI 使用的 flow 类型数据源
依赖项
requirements-gathering技能 — 如果当前对话上下文中尚无需求简报,则调用此技能生成简报function-creator技能 — 在初始化存储、为required: true架构编写先读后补丁惯用模式,或编写回填流时调用。$db仅限函数层;所有存储写入都通过函数执行datasource-creator技能 — 将存储读取封装为供 UI 使用的 flow 类型数据源时调用(这是在网格、选择器和报告分区中呈现存储数据的规范模式)impact-analysis技能 — 在进行任何可能破坏写入端调用方的架构更改之前调用:添加新的required: true列、将现有列改为required: true、重命名列,或移除调用方可能仍在读取的列。追踪$db.<Package>.<storage_referenceName>,找出所有需要添加先读后补丁回填或重命名更新的写入位置
CLI 生命周期
存储编写通过 dxs configuration 完成——这是适用于平台所有配置类型的通用 CRUD 基元。没有 dxs storage 子命令,也不支持字段级补丁;你需要构建(或获取并提取)整个 JSON 正文,编辑后再将整体推回。CLI 中的类型标识符为 storage(小写,与 ConfigurationEndpoints.normalize_type 的输出一致),对应 configurationTypeId: 17。
创建新存储:
# 1. Build body.json from scratch (see references/storage.md → Minimal Valid Skeleton)
# 2. Validate — gates the push. Exit 1 = errors found (read validation_errors, fix, re-run), not a broken CLI
dxs configuration validate storage -b <branchId> -D body.json
# 3. Create
dxs configuration upsert storage -b <branchId> -D body.json
编辑现有存储:
# 1. Fetch — note the envelope wrapper
dxs configuration get storage <configId> -b <branchId> -O envelope.json
# 2. EXTRACT THE INNER BODY (round-trip footgun guard — see "Round-trip rule" below)
jq .json envelope.json > body.json
# 3. Edit body.json
# 4. Validate — gates the push. Exit 1 = errors found (read validation_errors, fix, re-run), not a broken CLI
dxs configuration validate storage -b <branchId> -D body.json
# 5. Push
dxs configuration upsert storage -b <branchId> -D body.json
往返规则(至关重要)
编辑现有配置时,绝不能将 envelope.json 直接传递给 dxs configuration upsert——这会悄无声息地破坏配置内容。上文修正后的步骤(编辑前使用 jq 提取内部的 .json)适用于任何往返操作,必须遵守。有关规范的往返流程和底层错误,请参阅 ../datex-studio-shared/configuration-roundtrip.md。
存储的正文是最精简的配置类型之一——仅包含 objectTypeDef[](列架构)、组件身份信封,以及少数几个通常保持为 null 的顶层槽位。存储没有自己的代码字符串,因此没有 inParams / outParams / vars / events / flows 内容;所有读写逻辑都由调用方承担。仍须遵循往返规范(获取 → 使用 jq 提取 → 编辑 → 验证 → 推送)——无论正文大小如何,平台的导入路径都会执行完整的信封感知更新。
工作流
[Phase 1: Setup + Requirements]
Follow branch-setup.md for branch/connection selection
|
[requirements brief in context?]
+-----+-----+
| |
YES NO -> invoke `requirements-gathering`
| |
+-----+------+
|
[Phase 2: Decide storage vs Footprint entity]
Consult references/storage.md → Purpose & When to Use:
- storage: feature-owned data, no Footprint round-trip, no business-
object lifecycle, no navigation relationships to WMS entities,
simple column-set with implicit GUID identity
- first-class Footprint entity: data has business meaning beyond
the feature (customer, shipment, task), needs OData navigation,
has CRUD lifecycle reached via actions and OData datasources
If the decision lands on Footprint entity -> stop; this is not a
storage task. Otherwise continue.
|
[Phase 3: Author storage body]
Build body.json:
- File shape (configurationTypeId=17, isBlob=false unless intentional,
suffix -storage.json, referenceName ends _storage, snake_case
matches filename stem; description non-empty and <=100 chars;
accessModifier set (default public); feature package, not Utilities)
- Column descriptors (objectTypeDef[]) — inParam-shaped entries with
id (snake_case), type (string/number/boolean/date), isCollection
(false scalar, true array), required defaults to false, isSecured
typically null; remaining slots null
- No explicit `id` column — every record carries an implicit
platform-generated GUID `id` keyed by every $db predicate
- The `required: true` read-then-patch trap — defaulting `required`
to false avoids the partial-patch validation footgun; reserve true
only for columns where every future patch will always echo them;
when stuck with a legacy required column, write-side code MUST
read-then-patch (pull row, echo required fields into patch)
- Additive evolution rules — new nullable columns leave older rows
with null/undefined unless an explicit backfill runs in the same
edit; column drops require choosing the legacy-only off-schema
bracket-notation pattern OR the still-part-of-the-model
on-schema-required-false pattern based on intent
- Schema-change impact — for any required:true change, rename, or
removal, invoke `impact-analysis` skill with target
$db.<Package>.<storage_referenceName> before push to audit every
write-side call site
- Sibling slots — inParams / outParams / vars / events stay null;
storage has no code strings of its own
|
[Phase 4: Validate + push]
dxs configuration validate storage -b <branchId> -D body.json
|
+----+----+
| |
CREATE MODIFY-EXISTING
| |
| use the corrected round-trip
| (get -O envelope -> jq .json -> body)
| |
+----+----+
|
v
dxs configuration upsert storage -b <branchId> -D body.json
(upsert creates or updates by referenceName — one command for both)
|
[Phase 5: Verify in Studio (optional)]
Exercise the storage end-to-end through its callers — seed flow
populates rows, read datasource returns them, write flow applies
the read-then-patch idiom for required columns, schema change
doesn't break existing call sites
|
[invoke `post-edit-verification`; then `component-validator`]
阶段详情
阶段 1:设置 + 需求
- 按照 ../datex-studio-shared/branch-setup.md 选择分支和连接。切勿假设分支 ID——请用户确认。
- 检查对话上下文中是否已有需求说明(由
requirements-gathering或其他调用技能生成)。- 已有说明——使用该说明。说明应明确存储的数据内容(配置值、业务规则、每日快照等)、列及其类型、哪些列确实必须持久化、使用该存储的界面(写入数据的函数、供 UI 读取的数据源),以及该存储所属的功能包。
- 没有说明——先调用
requirements-gathering技能。预先确定列集合和required设置,可避免之后痛苦的架构迁移——存储上线后,required: true实际上只能新增(现有调用方尚未更新为传入新的必填列)。
阶段 2:决定使用存储还是 Footprint 实体
编写前,请参阅 references/storage.md → Purpose & When to Use。此决定将决定你是否使用了正确的技能。
适合使用存储的情况:
- 数据由功能拥有,且无需经过 Footprint OData 层往返。
- 无需将其设为一等 OData 实体——不需要与 WMS 实体建立导航关系,也没有业务对象生命周期(没有状态转换,除数据本身外也没有审计历史)。
- 需要简单的列集合和隐式 GUID 标识,而非带外键的 SQL 架构。
- 只能在函数层通过
$db.<Package>.<storage_referenceName>访问(操作或 UI 组件无法访问存储)。
适合使用一等 Footprint 实体的情况:数据具有超出该功能范围的业务含义,例如客户、货件或任务。这些数据属于平台 OData 架构,应通过 CRUD 操作和 OData 数据源访问。如果数据需要导航属性以关联其他 WMS 实体、其他功能也会读取数据,或数据需要完整的操作驱动型 CRUD 生命周期,请停止——这不是存储任务。
如有疑问,请问:“这些数据是否仅用于支持这一项功能的行为,还是代表了平台其他部分也可能关注的业务对象?”前者应使用存储;后者应使用 Footprint 实体。
阶段 3:编写存储正文
根据 references/storage.md → Minimal Valid Skeleton 中的框架创建 body.json。要点如下:
-
文件基础。 遵循下方的 预检清单 以及 ../datex-studio-conventions/universal-checklist.md;关于
-storage.json文件结构,请参阅 references/storage.md → File Location & Naming。构建时有两项存储专属注意事项:除非有意使用 Blob 式存储,否则设置isBlob: false;包的放置位置必须与功能相符(例如Inventory),而不是默认的Utilities——它决定$db.<Package>.<storage_name>访问路径。 -
列描述符。 每个
objectTypeDef[]条目都采用 inParam 结构。必需字段:id(列名,snake_case)、type("string"/"number"/"boolean"/"date")、isCollection(标量为false,数组列为true)。required默认为false。isSecured通常为false或null。其余所有字段(description、oneOf、fromBaseConfiguration、objectTypeDef、objectType、isConstant、constantValue)均保持为null。请参阅 references/storage.md → Column descriptors。 -
不要显式声明
id列。 每条存储记录都会自动带有一个由平台生成的 GUIDid字符串列。不要在objectTypeDef[]中声明id——它是隐式的。所有$db谓词都以它为键(i => i.id.equals(guid))。显式声明id会与隐式列冲突并导致读取失败。 -
required: true的读取后再补丁陷阱。 将列设为required: true会导致$db.update(id, patch)的部分补丁失败。平台会根据完整的已声明列架构验证补丁对象,因此,任何遗漏required: true列的补丁都会被拒绝,即使现有记录中已经填充了该字段。默认将每列的required设为false,并在流程层验证写入记录的数据。仅当某列 (a) 确实绝不能在静态存储状态下为空,且 (b) 今后的每次补丁都会包含该列时,才将其设为required: true。如果存储架构中已有无法放宽的required: true列,写入端代码必须先读取再补丁——先拉取该行,再构造包含这些必需字段的补丁对象。关于规范用法,请参阅 references/storage.md → Patching a record with required columns。 -
架构变更影响分析。 以下三类架构变更会影响多个环节,推送前必须进行影响分析:
- 新增
required: true列——要求$db.update的每个现有调用方都开始回传该列;更新前,现有调用方会静默地无法通过验证。 - 重命名列——所有引用旧名称的读写位置都会失效;生成的
IStorageItem_<Package>_<storage>类型也会改变结构。 - 移除历史记录中可能仍有值的列——即使 Mongo 仍会返回该字段,类型化点访问也会导致读取位置无法编译;应根据意图选择脱离架构的方括号表示法模式,或架构内的
required: false模式(权衡详情请参阅 references/storage.md → Column descriptors)。
对于以上任一情况,推送前都应针对
$db.<Package>.<storage_referenceName>调用impact-analysis技能,列出所有需要采用读取后补丁回传方式或更新重命名内容的写入端调用位置。不要由父 Agent 内联 grep 调用方——多文件扫描应由专用技能负责。 - 新增
-
增量演进。 新增可空列时,不会为现有记录回填数据。下游代码必须能够容忍旧行中的
null/undefined,或者在同一次编辑中运行一次性回填流程。应将回填与架构变更一并规划;不要发布会导致读取旧记录时崩溃的列。 -
同级槽位保持为 null。
inParams、outParams、vars、events在存储体中都保持为null——存储本身不包含代码字符串。所有读写逻辑都位于调用方(函数、流程类型数据源)中。遵循此约定;不要自行添加代码槽位。 -
符合调用层级要求。 存储仅限函数层级,通过
$db.<Package>.<storage_referenceName>调用。操作中不可用,UI 组件(选择器、网格、表单)也不可用。当 UI 界面需要存储数据时,应将读取操作封装在流程类型数据源中,并通过$flows.<Package>.<fn>调用函数;当操作需要写入存储时,应将写入操作封装在函数中,并通过$apis.<Package>.FootprintApi.extendedActions.<function>调用。请参阅 ../datex-studio-runtime/calling-conventions.md 和 ../db-query/references/db.md。
第 4 阶段:验证并推送
# Validate the body locally against the branch. Exit 1 = validation found errors
# (read validation_errors, fix body.json, re-run) — not a broken CLI. Do not push on exit 1.
dxs configuration validate storage -b <branchId> -D body.json
# For a new storage
dxs configuration upsert storage -b <branchId> -D body.json
# For modify-existing (round-trip — never skip the jq extract)
dxs configuration get storage <configId> -b <branchId> -O envelope.json
jq .json envelope.json > body.json
# ... edit body.json ...
dxs configuration upsert storage -b <branchId> -D body.json
验证会在推送前发现缺少必填字段、列描述符格式错误以及结构错误。但它无法捕获 required: true 的部分补丁陷阱(存储验证正常;运行时错误会在调用方写入时出现)、缺少新增列回填的缺口(较旧的行会返回 null,下游代码可能因此崩溃),也无法发现架构变更对调用方造成的影响(重命名会悄无声息地破坏所有未修改的调用点)。推送前,请逐项检查 references/storage.md → 推送前检查清单(下方清单与其内容一致)。
第 5 阶段:在 Studio 中验证(可选)
通过调用方对存储进行端到端测试:
- 种子数据/初始化流程会以幂等方式填充预期的初始行(重复运行不会产生重复项)。
- 读取流程或流程类型数据源会按预期列结构返回行;谓词 DSL 运算符(
.equals/.and/.or/.isNull/.in)能按编写方式正常工作。 - 针对包含
required: true列的行执行写入的流程,能成功应用先读取再打补丁的惯用模式——补丁会回显必填列,更新也能成功。 - 对于架构变更(添加列/更改必填属性/重命名/移除列),所有经过审查的写入端调用点都能继续正常工作——没有悄无声息的验证失败,也没有过时的点访问编译错误。
- 每次读取行时都包含隐式的
idGUID 列;以该列为依据的谓词能解析到预期的行。
如果无法运行应用,请重新获取配置(使用修正后的 jq .json 提取模式),并与 body.json 进行差异比较,以确认推送已生效。
发布前检查清单
推送前,请完整检查 references/storage.md → 发布前检查清单。快速版:
- 文件基础检查。
configurationTypeId: 17、isBlob: false(除非有意使用 Blob 模式)、后缀为-storage.json、组件名称以_storage结尾;此外还要通过通用检查(../datex-studio-conventions/universal-checklist.md)。 - 包的放置位置与功能相符(例如
Inventory),而不是Utilities。这决定了$db.<Package>.<storage_name>访问路径。 - 列定义完整。 每个
objectTypeDef[]条目都包含id、type、isCollection。未使用的槽位保持为null。 - 不要显式定义
id列。 平台会添加隐式 GUID。 - 每列的
required默认值都为false。 在流程层进行验证。只有在今后的每次补丁都会始终包含该列时,才设置required: true;如果旧版架构中该列已经是required: true,每次调用$db.update都必须先读取再打补丁,并回传必需字段。 - 确保架构变更安全。 新增列:调用方必须能够容忍现有行中的
null,或者在同一次编辑中执行数据回填。更改required: true的列、重命名或移除列:在$db.<Package>.<storage_referenceName>上运行impact-analysis技能,并在合并前审查每个命中项——切勿在父级中直接通过 grep 查找调用方。 - 同级槽位保持为
null。inParams、outParams、vars、events均保持为null——存储自身没有代码字符串。 - 使用方位于正确层级。
$db调用方必须是函数(-flow.json、configurationTypeId: 9),或是流程类型数据源中的流程槽位。操作无法访问$db;UI 组件无法访问$db。 - 谓词使用流式 DSL。 使用
.equals(...)/.and(...)/.or(...)/.isNull()/.in([...]),切勿使用===、&&、||。(TypeScript 接受原生运算符且不会报错,但运行时谓词会在转换过程中报错,或者逻辑会悄然失效。)
常见错误
| 错误 | 修复方法 |
|---|---|
| 数据本应是一级 Footprint 实体,却选择了存储 | 存储适用于由功能拥有、没有业务对象生命周期且没有导航关系的数据。如果数据是客户 / 货运 / 任务或其他 WMS 级概念,应将其建模为 Footprint 实体,并通过 CRUD 操作 + OData 数据源访问。 |
在 objectTypeDef[] 中声明了显式的 id 列 | 删除它。平台会为每条记录添加一个隐式 GUID id 字符串列;显式声明会与隐式列冲突并导致读取失败。 |
为了保险起见,将每一列都设为 required: true | 这会颠倒部分更新语义——此后,每次 $db.update(id, patch) 都必须回传每个必填列,即使只修改了一列。默认将 required 设为 false,并在流程层进行验证;仅当每次后续更新都会包含该列时,才设为 true。 |
添加新列后,$db.update(id, patch) 因验证错误而失败 | 可能是新列设为 required: true,而调用方没有回传该列(先读取再更新的惯用模式);也可能是针对完整架构验证更新内容。将新列的 required 默认设为 false,或调用 impact-analysis 并更新每个写入位置,使其回传这个新的必填列。 |
| 添加了新的可空列;读取旧记录时因预期该字段存在而崩溃 | 新列不会回填。要么在读取位置处理 null / undefined,要么在同一次编辑中运行一次性回填流程。 |
| 删除了某列,但尚未更新的读取位置因无法编译而失败(对现在未声明的字段进行类型化点访问) | 根据意图选择:“该列已删除” → 不要将其保留在 objectTypeDef[] 中,并在旧读取位置使用方括号记法进行非类型化访问(row['retired_field'] as string | null);“仍属于模型,只是可空” → 将其保留在架构中并设为 required: false。这两种模式表达的意图不同——不要混淆。 |
将存储放在 Utilities 下,而不是功能包中 | 包决定 $db.<Package>.<storage_name> 访问路径。将其放在功能包下(例如 Inventory、Acme),以便调用方通过 $db.Inventory.<storage> 访问,而不是 $db.Utilities.<storage>。 |
description 超过 100 个字符 | SQL 列长度限制——推送将无法通过验证。请缩短。 |
referenceName 不以 _storage 结尾,或文件名主干与 referenceName 不匹配 | 违反命名约定——_storage 后缀是类型标识,与 _dd / _hub / _form / _editor 类似。文件名主干必须与 referenceName 完全匹配。 |
谓词使用原生 TypeScript 运算符(===、&&、` | |
操作代码尝试通过 $db 读取存储 | $db 仅适用于函数层——操作中不可用,也不会向 UI 组件公开。请将读取操作封装在函数中,并通过 $apis.<Package>.FootprintApi.extendedActions.<function> 在操作中调用该函数。 |
| 重命名了存储列,但没有更新调用方 | 每个引用旧名称的读写位置都会失效。请先对 $db.<Package>.<storage_referenceName> 调用 impact-analysis 技能,然后在同一次编辑中更新所有命中项。 |
将 dxs configuration get -O envelope.json 的输出直接传入 dxs configuration upsert -D envelope.json | 会悄无声息地破坏配置内容。编辑前务必先运行 jq .json envelope.json > body.json。请参阅上文“往返规则”。 |
编辑完成后,调用 post-edit-verification,以发现 description/JSON/架构违规。最终检查时,调用 component-validator。