openxiangda 2.14.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  # NestJS 后端
2
2
 
3
- 默认模板只包含 Web 和共享契约。只有需要执行服务端业务动作时才增加 NestJS。记录列表、详情、新增、编辑和删除直接由浏览器调用平台 Data API,不在 controller 中重写一遍。
3
+ 默认模板只包含 Web 和共享契约。只有需要执行服务端业务动作时才增加 NestJS。记录列表、详情、新增、编辑和删除直接由浏览器调用平台 Data API,不在 controller 中重写一遍。启用前先按[判定是否真的需要 Nest 后端](./development.md#backend-decision)逐行核对:幂等、时间窗、状态前置、角色核对、聚合、导入导出都有声明式答案;只有真实外部副作用或无法声明的跨资源不变量才是启用理由。`check` 会拒绝未以 `@OpenXiangdaOperation(appOperations.<code>)` 绑定已声明 operation 的应用路由。
4
4
 
5
5
  平台网关验证当前用户完整的应用角色并集,并把经平台重验的角色、capability 与可选 Perspective 交给 Nest SDK。业务 controller 使用生成的 operation 合同和 capability 装饰器;平台仍是身份与授权的唯一所有者。请求作用域 `OpenXiangdaDataApiService` 自动继承 Perspective 读取投影;绕过 Data API 的自定义读取才使用 `@CurrentPerspective()` 显式投影。应用代码不替换身份、不保存平台凭据,也不建立第二套用户或权限状态。
6
6
 
@@ -74,6 +74,41 @@ ISO 时间字符串;不接受空值、嵌套路径、引用或表达式。offs
74
74
  最多正负 366 天的整数,所有时间条件共享一个接受时刻。需要平台 Data API 1.1.0。
75
75
 
76
76
 
77
+
78
+ ### 事务内写入快照字段 {#snapshot-values}
79
+
80
+ option / user / department / resource-ref 字段在 Data API 与事务写入里保存 `{ label, value }` 显示快照,`value` 是比较键。写裸字符串会被字段校验拒绝(`OPENXIANGDA_NATIVE_DATA_OBJECT_REQUIRED`)。使用助手函数避免手写形状:
81
+
82
+ ```ts
83
+ import { optionSnapshot, userSnapshot, resourceSnapshot } from 'openxiangda/nest';
84
+
85
+ data: {
86
+ status: optionSnapshot('处理中', 'processing'),
87
+ assignedTechnician: userSnapshot(input.technicianId),
88
+ requestId: resourceSnapshot('repair-requests', input.requestId, title),
89
+ }
90
+ ```
91
+
92
+ 读取判断状态用 `record.data.status?.value === 'pending'`。
93
+
94
+ ### 幂等冲突复核 {#idempotency-recovery}
95
+
96
+ 同一 `idempotencyKey` 要求内容指纹一致。update 事务携带 `expectedRevision`,重试时 revision 已前进会触发 409 `OPENXIANGDA_NATIVE_DATA_IDEMPOTENCY_CONFLICT`。捕获后回读当前状态确认效果已生效,按幂等结果返回;不要换新键重试:
97
+
98
+ ```ts
99
+ import { isIdempotencyConflict } from 'openxiangda/nest';
100
+
101
+ try {
102
+ result = await this.data.transaction(idempotentTransaction(key, operations, guards));
103
+ } catch (error) {
104
+ if (isIdempotencyConflict(error) && alreadyApplied(await this.data.get(...))) {
105
+ return { idempotencyKey: key, replayed: true, ...currentState };
106
+ }
107
+ throw error;
108
+ }
109
+ ```
110
+
111
+ 事务守卫的 `errorCode` 必须匹配 `^OPENXIANGDA_[A-Z0-9_]{1,96}$`,例如 `OPENXIANGDA_REPAIR_REQUEST_NOT_PENDING`。
77
112
  ## 业务动作与普通查询 {#business-action}
78
113
 
79
114
  `OpenXiangdaDataApiService` 按当前用户的普通资源、行和字段权限执行。具名业务动作使用 `OpenXiangdaBusinessDataApiService`:入口先检查该动作 capability,平台在精确应用和环境内以受信任后端执行,并保留发起人与动作审计。业务动作不能接受任意模型/字段/用户 ID 后不做业务校验;应用负责该动作的输入约束和业务不变量。
@@ -0,0 +1,188 @@
1
+ # 声明速查:一次写对 openxiangda.config.ts {#cheatsheet}
2
+
3
+ 按"错误码 → 规则 → 正确片段"组织。这些规则全部来自真实返工:先扫一遍本页,再写声明,能省掉绝大多数首轮校验迭代。普通 CRUD 的完整可过检骨架见文末。
4
+
5
+ ## 模块与 CRUD 视图
6
+
7
+ | 规则 | 正确写法 |
8
+ | --- | --- |
9
+ | `crud[].model` 必须引用本模块已声明的模型 | `crud: [{ model: 'repair-requests', ... }]` |
10
+ | 视图 `list`/`form`/`detail` 的 `model` 可省略(继承视图模型);显式声明时必须与 `crud[].model` 一致 | `list: { fields: [...] }` 即可,不必写 `model` |
11
+ | 每个模型最多 20 个命名视图;命名视图需要稳定 `code` + `name` | `crud: [{ model: 'x', code: 'x-active', name: '进行中', ... }]` |
12
+ | 新建视图的 `form.fields` 必须覆盖全部无默认必填字段,或显式 `generated.create: false` | 检查器会列出缺失字段 |
13
+
14
+ ## 字段声明
15
+
16
+ | 规则 | 正确片段 |
17
+ | --- | --- |
18
+ | `option.*` 字段的值是 `{ label, value }` 快照(比较键是 `value`) | `options: [{ label: '教学设备', value: 'teaching' }]` |
19
+ | `number.integer` 可省略精度;只允许 `precision`(位数),不允许 `scale` | `{ code: 'qty', type: 'number.integer' }` |
20
+ | `number.decimal` 的 `precision` 必填、`scale` 0 到 precision | `{ type: 'number.decimal', precision: 12, scale: 2 }` |
21
+ | `audit.read` 可写 `true`(绑定本资源读能力)或能力数组 | `audit: { read: true }` |
22
+ | `resource-ref.*` 必须带 `source` 来源协议 | `{ type: 'resource-ref.single', source: { kind: 'resource', resourceCode: 'repair-requests', labelField: 'title', searchFields: ['title'], pageSize: 20, loadMode: 'search' } }` |
23
+ | `labelField` 必须指向目标资源的 `text.short` / `text.long` 字段 | 不要用流水号/选项字段当 label |
24
+ | 子表 `subtable` 的外键是子资源的 **uuid** 字段,排序字段是**可写 number.integer** | 子资源:`{ code: 'requestId', type: 'uuid', required: true }` + `{ code: 'sortOrder', type: 'number.integer', required: true }`;父表:`subtable: { resourceCode: 'repair-items', foreignKey: 'requestId', orderField: 'sortOrder', maxRows: 20 }` |
25
+ | 图片/附件的 `file` 限定数量与大小 | `file: { maxCount: 3, maxSizeMb: 10, accept: ['image/png', 'image/jpeg'] }` |
26
+
27
+ ## 权限声明
28
+
29
+ | 规则 | 正确片段 |
30
+ | --- | --- |
31
+ | 平台保留能力(如 `app:<app>:directory:read`)**不能**在 `capabilities` 里重复声明,直接在角色中引用即可 | `const directoryRead = \`app:\${APP_CODE}:directory:read\`` → `roles: [{ code: 'admin', capabilities: [directoryRead] }]` |
32
+ | 资源 CRUD 能力码用 `resourceCapabilityCodes(appCode, resourceCode)` 生成 | `const crud = resourceCapabilityCodes(APP_CODE, 'repair-requests')` → `capabilities: [crud.read, crud.create]` |
33
+ | `authenticatedUserRoleCode` 是平台登录用户的基线角色 | `authz: { authenticatedUserRoleCode: 'app-user', ... }` |
34
+
35
+ ## 后端操作(Nest)
36
+
37
+ | 规则 | 正确片段 |
38
+ | --- | --- |
39
+ | 写操作的 `ai.sideEffects` 至少一条具体副作用 | `ai: { name: '受理派单', ..., risk: 'write', sideEffects: ['更新报修单状态为处理中', '写入一条派工记录'] }` |
40
+ | GET 操作的 `ai.risk` 只能是 `read`;写操作不能是 `read` | `risk: 'read'` ↔ `method: 'GET'` |
41
+ | controller 路由必须绑定 `@OpenXiangdaOperation(appOperations.<code>)`,普通 CRUD 不写 controller | check 门禁会拒绝未绑定路由 |
42
+ | 事务守卫 `errorCode` 必须匹配 `^OPENXIANGDA_[A-Z0-9_]{1,96}$` | `errorCode: 'OPENXIANGDA_REPAIR_REQUEST_NOT_PENDING'` |
43
+
44
+ ## 事务写入快照字段
45
+
46
+ option / user / department / resource-ref 字段在事务和普通写入里都必须写快照对象,不能写裸字符串:
47
+
48
+ ```ts
49
+ import { optionSnapshot, userSnapshot, resourceSnapshot } from 'openxiangda/nest';
50
+
51
+ await this.data.transaction(idempotentTransaction(input.idempotencyKey, [
52
+ {
53
+ operation: 'update',
54
+ resourceCode: 'repair-requests',
55
+ id: input.requestId,
56
+ expectedRevision: revision,
57
+ data: {
58
+ status: optionSnapshot('处理中', 'processing'), // 不是 'processing'
59
+ assignedTechnician: userSnapshot(input.technicianId), // 不是裸 userId
60
+ },
61
+ },
62
+ {
63
+ operation: 'create',
64
+ resourceCode: 'repair-assignments',
65
+ data: {
66
+ requestId: resourceSnapshot('repair-requests', input.requestId, requestTitle),
67
+ technician: userSnapshot(input.technicianId),
68
+ },
69
+ },
70
+ ]));
71
+ ```
72
+
73
+ 读取时状态判断用 `record.data.status?.value === 'pending'`(存储值是快照对象)。
74
+
75
+ ## 幂等冲突复核模式
76
+
77
+ update 事务必须携带 `expectedRevision`;重试时 revision 已前进会让同一幂等键的内容指纹漂移,平台返回 409 `OPENXIANGDA_NATIVE_DATA_IDEMPOTENCY_CONFLICT`(而非 `replayed: true`)。标准处理:
78
+
79
+ ```ts
80
+ import { isIdempotencyConflict } from 'openxiangda/nest';
81
+
82
+ try {
83
+ result = await this.data.transaction(idempotentTransaction(key, operations, guards));
84
+ } catch (error) {
85
+ if (isIdempotencyConflict(error)) {
86
+ const current = await this.data.get('repair-requests', input.requestId);
87
+ if (/* 状态已离开 pending,说明本键的效果已生效 */) {
88
+ return { idempotencyKey: key, replayed: true, ... 当前状态 };
89
+ }
90
+ }
91
+ throw error;
92
+ }
93
+ ```
94
+
95
+ 不要用新生成的幂等键重试冲突——那会绕过幂等保护重复执行业务动作。
96
+
97
+ ## 两模型起步骨架(可直接改造)
98
+
99
+ ```ts
100
+ import {
101
+ adminNavigationGroup, adminResourcePage, defineAdminNavigation,
102
+ defineApplicationModule, defineOpenXiangdaApp, resourceCapabilityCodes,
103
+ } from 'openxiangda/config';
104
+
105
+ const APP_CODE = 'my-app';
106
+ const requestCrud = resourceCapabilityCodes(APP_CODE, 'requests');
107
+
108
+ const requests = {
109
+ code: 'requests', name: '申请单',
110
+ audit: { read: true },
111
+ fields: [
112
+ { code: 'title', type: 'text.short', label: '标题', required: true },
113
+ { code: 'category', type: 'option.single', label: '类别', required: true,
114
+ options: [{ label: '普通', value: 'normal' }, { label: '紧急', value: 'urgent' }] },
115
+ { code: 'status', type: 'option.single', label: '状态', required: true,
116
+ options: [{ label: '待受理', value: 'pending' }, { label: '已完成', value: 'done' }] },
117
+ { code: 'photo', type: 'image', label: '照片', file: { maxCount: 3, maxSizeMb: 10 } },
118
+ ],
119
+ };
120
+ const items = {
121
+ code: 'request-items', name: '明细',
122
+ fields: [
123
+ { code: 'requestId', type: 'uuid', required: true },
124
+ { code: 'sortOrder', type: 'number.integer', required: true },
125
+ { code: 'name', type: 'text.short', required: true },
126
+ { code: 'qty', type: 'number.integer' },
127
+ { code: 'price', type: 'number.decimal', precision: 12, scale: 2 },
128
+ ],
129
+ };
130
+
131
+ export default defineOpenXiangdaApp({
132
+ app: { code: APP_CODE, name: '我的应用' },
133
+ frontend: {
134
+ root: 'apps/web',
135
+ devicePolicy: { kind: 'viewport-family', mobileMaxWidthPx: 900, desktopMinWidthPx: 901 },
136
+ routes: [
137
+ { code: 'application-home', path: '/home', label: '应用首页', surface: 'user' },
138
+ { code: 'application-home-mobile', path: '/m/home', label: '移动首页', surface: 'user' },
139
+ ],
140
+ authentication: {
141
+ accountMode: 'existing-platform-users-only',
142
+ registration: { mode: 'reject' },
143
+ methods: [{ code: 'password', type: 'password', label: '账号密码登录', presentation: 'primary', required: true }],
144
+ surfaces: {
145
+ desktop: { routeCode: 'application-login', path: '/login', defaultRouteCode: 'application-home' },
146
+ mobile: { routeCode: 'application-login-mobile', path: '/m/login', defaultRouteCode: 'application-home-mobile' },
147
+ },
148
+ },
149
+ admin: {
150
+ navigation: defineAdminNavigation([
151
+ adminNavigationGroup('main', '业务管理', [adminResourcePage('requests', { label: '申请单' })], { icon: 'database', order: 100 }),
152
+ ]),
153
+ },
154
+ },
155
+ modules: [defineApplicationModule({
156
+ code: 'main',
157
+ models: [requests, items],
158
+ crud: [
159
+ {
160
+ model: 'requests',
161
+ list: {
162
+ fields: ['title', 'category', 'status'],
163
+ filterFields: ['category', 'status'],
164
+ searchableFields: ['title'],
165
+ defaultPageSize: 20,
166
+ defaultSort: { field: 'title', order: 'asc' },
167
+ },
168
+ mobile: { enabled: true },
169
+ },
170
+ ],
171
+ })],
172
+ authz: {
173
+ authenticatedUserRoleCode: 'app-user',
174
+ capabilities: [],
175
+ roles: [
176
+ { code: 'app-user', name: '应用用户', capabilities: [requestCrud.read, requestCrud.create] },
177
+ { code: 'admin', name: '管理员', capabilities: [requestCrud.read, requestCrud.create, requestCrud.update, requestCrud.delete] },
178
+ ],
179
+ scopeDimensions: [], scopeSources: [], dataPolicies: [], authorizationTransitions: [],
180
+ },
181
+ });
182
+ ```
183
+
184
+ `request-items` 不出现在 `crud` 里:子表行随 `requests` 表单的 `subtable` 字段写入(在 `requests.fields` 里补 `{ code: 'items', type: 'subtable', subtable: { resourceCode: 'request-items', foreignKey: 'requestId', orderField: 'sortOrder', maxRows: 20 } }`)。
185
+
186
+ ## 图片上传的像素上限
187
+
188
+ image/signature/富文本图片字段在上传计划(initiate)里返回 `maxPixels`;超过上限的图片会被标准组件自动压缩后重新发起上传。用 API 直传时自行按 `maxPixels` 预检。当前平台上限覆盖主流手机主摄(48/50/64MP);超出会得到带实际尺寸的 `OPENXIANGDA_NATIVE_DATA_IMAGE_PIXEL_LIMIT_EXCEEDED` 错误。
@@ -4,13 +4,10 @@
4
4
 
5
5
  ## 测试部署
6
6
 
7
- 托管源码应用通过 `pnpm openxiangda source push -m "本轮变更说明"` 完成提交与推送。
8
- 发布系统在平台绑定仓库中核验精确源码提交,并沿用 AppVersion 的源码和制品摘要关联。
9
- 本地构建仍是当前交付方式;源码提交存在不等于平台已独立验证制品由该提交构建。
7
+ 源码托管仍可通过 `pnpm openxiangda source push` 提交和推送,但不是测试发布的前置步骤。
8
+ 发布使用当前工作区内容,源码提交、分支名称和是否存在未提交修改不会阻塞测试环境;包元数据会保留实际来源信息。
10
9
 
11
- 发布前,先将本轮源码、生成契约及必要记录合入并推送仓库的远端默认主分支,然后从干净且同步的主分支工作区发布。工具从 origin 的远端 HEAD 识别主分支,不把任务分支的 upstream 当作主线。未提交、未推送、未合并或落后主线的问题会在构建和上传前返回;工具不会自动合并分支或覆盖其他会话的改动。
12
-
13
- 开发开始时先同步主线并读取项目现状,开发完成包括提交、推送与主线整合。每个工作区保持一个写者;需要并行时使用独立目录并明确各任务范围。日常 dev/check 仍可验证未提交源码。没有 Git 远端的项目应先建立并绑定仓库再发布。
10
+ 开发者可以从当前任务分支或本地工作区直接发布。团队是否要求合入默认分支属于协作规范,不由 Devkit 在每次发布时重复执行。
14
11
 
15
12
  准备部署时直接执行 deploy,它已经包含兼容性预检、生成、检查、测试和构建。只想检查代码时使用 [check](./testing.md),无需在 deploy 前重复运行全套检查。
16
13
 
@@ -37,7 +34,7 @@ pnpm openxiangda status <deployment-id> --watch
37
34
 
38
35
  网络响应不确定时先查询原运行,不凭本地输出创建重复部署。平台部署成功后,仍需执行真实角色的业务验收。
39
36
 
40
- 相同源码候选重试时,工具会重新核对本地验证证据、封存清单和制品字节,复用仍有效的构建结果及后端镜像;已上传内容按摘要查询并复用。只有环境条件改变时不需要重建源码制品。输出损坏、输入变化或缓存缺失时自动回到正式检查和构建。缓存位于 `.openxiangda/build/`,不是新的部署状态源;提交响应不确定时使用原候选和幂等键,已有失败运行按其 recovery 恢复。
37
+ 重复发布直接使用当前源码重新构建。pnpm、Docker 和平台可以自行提供构建缓存,Devkit 不维护另一套工作区摘要或候选状态。提交响应不确定时使用原 DeploymentRun 的 status/logs recovery 继续处理。
41
38
 
42
39
  ## 测试环境验收
43
40
 
@@ -27,6 +27,27 @@
27
27
  - 标准审批、待办与通知:按需声明平台能力,见[工作流](./workflow-events.md)。
28
28
  - 真实事务或外部集成:使用[按需后端](./backend.md),不为每张表重写 CRUD 控制器。
29
29
 
30
+ ### 判定是否真的需要 Nest 后端 {#backend-decision}
31
+
32
+ 想写后端接口时,先按顺序核对平台已有的声明式答案;命中前几行的需求不得启用 Nest。
33
+ 普通数据增删改查永远由浏览器直接调用平台 Data API 或标准 CRUD 页面完成,
34
+ 应用 controller 不做记录列表、详情、新增、编辑、删除的转发。
35
+
36
+ | 你以为需要写后端 | 平台已有的声明式答案 | 参考 |
37
+ | --- | --- | --- |
38
+ | 列表、筛选、排序、分页接口 | `createNativeResourceClient` 的 `list`,服务端条件树与分页 | [前端数据访问](./frontend.md#data-access) |
39
+ | 新增 / 编辑 / 删除接口 | 标准 CRUD 页面,或同一客户端的 `create` / `update` / `remove`(`expectedRevision` 乐观锁) | [前端数据访问](./frontend.md#data-access) |
40
+ | 提交防重、幂等重试 | `transactNativeData` / 事务请求自带 `idempotencyKey` 幂等回执 | [前端数据访问](./frontend.md#data-access)、[按需后端](./backend.md#business-action) |
41
+ | 时间窗、状态前置、指定人角色校验 | 平台事务守卫:`operation-time`、`record-assert`、`record-exists`、`role-member` | [按需后端](./backend.md#business-action) |
42
+ | 统计报表数据 | Data API 服务端聚合 `batchAggregateNativeResources`(单个指标也用它),前端不拉全量求和 | [前端](./frontend.md#component-selection) |
43
+ | 导入 / 导出 | 标准 CRUD 的 `import` / `export` 动作声明 | [业务模块](./application-foundation.md) |
44
+ | 跨模型原子写、外部 API、硬件或第三方推送 | Nest 具名 operation + 平台事务,必要时事务内 `emitEvent` | [按需后端](./backend.md) |
45
+
46
+ 启用 Nest 的唯一充分条件是:真实外部副作用,或现有守卫无法声明的跨资源业务不变量,
47
+ 且该动作已作为 operation 声明能力(`kind: 'backend'`)并由角色显式引用。
48
+ "需要一点校验""需要默认值""需要联动查询"不是启用理由;校验优先字段规则与事务守卫,
49
+ 默认值优先服务端字段责任,联动查询优先 Data API 条件树。
50
+
30
51
  ## 实施与交接 {#iteration}
31
52
 
32
53
  开发使用 `pnpm openxiangda dev`,过程中运行必要的聚焦测试。交接前按[检查与验收](./testing.md)验证;授权发布后按[交付](./delivery.md)部署。失败保留错误码、位置和原始候选,依据平台恢复指令继续。
@@ -63,6 +63,43 @@ capability、应用角色和数据策略。前端只根据当前登录用户完
63
63
  可替换 Data API adapter。不要调用自定义 Nest CRUD、Function 或 Workflow 来绕过
64
64
  Data API。只有真正需要事务或外部系统的动作才使用同源 `/api`。
65
65
 
66
+ ### 自定义页面消费平台数据 {#data-access}
67
+
68
+ 自定义报表、工具页和用户端页面从 `openxiangda/core` 导入 `createNativeResourceClient`,
69
+ 用生成契约里的 surface 直接获得该资源的权威读写客户端;行、字段与操作授权由平台在
70
+ 每次请求时执行,页面不需要也不得复制权限逻辑:
71
+
72
+ ```tsx
73
+ import { createNativeResourceClient } from 'openxiangda/core';
74
+ import { resourceSurfaces } from '@app/contracts';
75
+
76
+ const records = createNativeResourceClient('records', resourceSurfaces.records);
77
+
78
+ // 服务端过滤、排序、分页;字段必须已声明,未声明字段直接报错
79
+ const page = await records.list({
80
+ page: 1,
81
+ pageSize: 20,
82
+ where: { field: 'enabled', operator: 'eq', value: true },
83
+ sort: { field: 'createdAt', order: 'desc' },
84
+ });
85
+
86
+ const record = await records.get(id);
87
+ await records.create(data);
88
+ await records.update(id, expectedRevision, data); // revision 冲突时明确报错
89
+ await records.remove(id, expectedRevision);
90
+ await records.upload('attachment', file, recordId);
91
+ ```
92
+
93
+ 跨模型原子写使用 `transactNativeData(operations, idempotencyKey)`:一次平台事务提交
94
+ 多个资源操作,携带幂等键,不确定的响应复用同一载荷重试,不产生第二份业务效果。
95
+ 多指标统计使用 `batchAggregateNativeResources` 在服务端聚合。导出使用
96
+ `records.exportCsv(query, select)`,与列表共用同一查询条件。
97
+
98
+ 要求服务端校验、状态前置或角色核对时,优先事务守卫(见
99
+ [判定是否真的需要 Nest 后端](./development.md#backend-decision));只有在真实外部
100
+ 副作用下才声明 Nest operation。自写 controller 转发单一资源的增删改查无法通过 `check`:
101
+ 每个应用路由必须以 `@OpenXiangdaOperation(appOperations.<code>)` 绑定已声明的 operation。
102
+
66
103
  默认仪器模块有 30 个字段,其中 `id/revision` 是 Data API 系统字段,28 个业务
67
104
  字段由资源声明。新增、编辑和详情共用同一份字段元数据。五个边界字段使用五个独立
68
105
  capability,不使用角色名或影子字段判断。
@@ -66,10 +66,10 @@ MCP 服务随项目根包一起安装,AI 客户端的 stdio 连接仍需配置
66
66
  以下命令的版本占位符由随包资料替换为该根包的精确版本。网站源码阅读者应先确认要使用的发行版本。
67
67
 
68
68
  ```bash
69
- pnpm dlx openxiangda@2.14.0 skill install --force
70
- pnpm dlx openxiangda@2.14.0 auth status --base-url <平台地址> --json
71
- pnpm dlx openxiangda@2.14.0 login --cwd my-app --base-url https://platform.example.com
72
- pnpm dlx openxiangda@2.14.0 create my-app --base-url https://platform.example.com
69
+ pnpm dlx openxiangda@2.16.0 skill install --force
70
+ pnpm dlx openxiangda@2.16.0 auth status --base-url <平台地址> --json
71
+ pnpm dlx openxiangda@2.16.0 login --cwd my-app --base-url https://platform.example.com
72
+ pnpm dlx openxiangda@2.16.0 create my-app --base-url https://platform.example.com
73
73
  cd my-app
74
74
  pnpm openxiangda context --json
75
75
  pnpm openxiangda dev
@@ -171,9 +171,9 @@ MCP 的 `docs_read` 可以读取本说明,当前没有独立的源码操作 MC
171
171
  无需本地工作区,使用本 Skill 随包精确版本或已安装的对应 CLI:
172
172
 
173
173
  ```bash
174
- pnpm dlx openxiangda@2.14.0 auth status --base-url <平台> --json
175
- pnpm dlx openxiangda@2.14.0 source resolve <仓库URL> --base-url <平台> --json
176
- pnpm dlx openxiangda@2.14.0 source clone <仓库URL> <新目录> --base-url <平台> --json
174
+ pnpm dlx openxiangda@2.16.0 auth status --base-url <平台> --json
175
+ pnpm dlx openxiangda@2.16.0 source resolve <仓库URL> --base-url <平台> --json
176
+ pnpm dlx openxiangda@2.16.0 source clone <仓库URL> <新目录> --base-url <平台> --json
177
177
  ```
178
178
 
179
179
  登录缺失或站点不匹配时,先按该平台执行 login。resolve 根据平台已经登记的绑定返回
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": "openxiangda.documentation/v1",
3
- "version": "2.14.0",
3
+ "version": "2.16.0",
4
4
  "topics": [
5
5
  {
6
6
  "id": "getting-started",
7
7
  "title": "安装与开始开发",
8
8
  "file": "getting-started.md",
9
- "sha256": "f029bedc4233708e540b4a449319d78d51a804b6fef85e19a6d03b3db0de91ba"
9
+ "sha256": "17f4f79e99dd5880c368a3326c066c0b6420f62d9b61961bc3b23afc25a5b772"
10
10
  },
11
11
  {
12
12
  "id": "product-design",
@@ -42,7 +42,13 @@
42
42
  "id": "development",
43
43
  "title": "需求与开发流程",
44
44
  "file": "development.md",
45
- "sha256": "02e8d59b349a3c58dd3420a777602394cefb42f49f26d2c6bbf7065fcfa0660b"
45
+ "sha256": "c172cf5a6a20340b4142a1443b653d3e312f8a4464f990e69055046033011366"
46
+ },
47
+ {
48
+ "id": "declarations-cheatsheet",
49
+ "title": "声明速查:一次写对 config",
50
+ "file": "declarations-cheatsheet.md",
51
+ "sha256": "6938d50f86066a7ad01aff495fbfb3daf49ba56a0d19cdd35f1cba6257047e78"
46
52
  },
47
53
  {
48
54
  "id": "application-foundation",
@@ -66,7 +72,7 @@
66
72
  "id": "frontend",
67
73
  "title": "页面与标准组件扩展",
68
74
  "file": "frontend.md",
69
- "sha256": "30897a79272e979767ac16c0814f5eea79a67341797ee05287e56f93c31ecc0b"
75
+ "sha256": "61eedc337b1e64d404f5521eedf3ed6e3afc7ac341a4c56972ffe1000bb9bfd4"
70
76
  },
71
77
  {
72
78
  "id": "field-components",
@@ -96,7 +102,7 @@
96
102
  "id": "backend",
97
103
  "title": "按需后端与业务动作",
98
104
  "file": "backend.md",
99
- "sha256": "3a800c16f2220dadad028c8807374dbda81afd94d669b6f84d8dd774b6627957"
105
+ "sha256": "75c8f9b83c37afe53b57d0283fa814a5153aa7d72a85efb2a1d9c4a9c1844e6a"
100
106
  },
101
107
  {
102
108
  "id": "administration",
@@ -108,13 +114,13 @@
108
114
  "id": "testing",
109
115
  "title": "检查与真实业务验收",
110
116
  "file": "testing.md",
111
- "sha256": "9d98b0cbfd6df55436bc85fa54e10ad7adce5d8ef3a4a170f32dffa379d96ea0"
117
+ "sha256": "241f800e906213a3bc5a6dbd8931f9a6d55cb0d01d0e5f5be355e7bec6078f5f"
112
118
  },
113
119
  {
114
120
  "id": "delivery",
115
121
  "title": "部署、生产晋级与恢复",
116
122
  "file": "delivery.md",
117
- "sha256": "b4456af406889bb9dbd296d6f445baa6ec7b1f25756d5d518adb245f1fc095ae"
123
+ "sha256": "32f07cfe6b946975bb3795e6ca3a01370e4d6bcc89f1b52b8df19085438e92c2"
118
124
  },
119
125
  {
120
126
  "id": "upgrading",
@@ -18,7 +18,7 @@ CI、离线开发或尚未发布的候选包使用 `pnpm openxiangda check --loc
18
18
 
19
19
  检查会写本地生成结果并按需初始化后端,不是只读操作。前置阶段失败后,下游阶段标为 skipped,不继续构建或上传。保留错误码、pointer、details 和下一步,修正原因后重试。
20
20
 
21
- 源码、锁文件、依赖安装状态、工具链、构建环境和输出摘要均未变化时,检查自动复用已通过的 check/test/build,并在阶段结果标记 `reused: true`。目标平台的权限、密钥和模型条件每次重新预检。检查期间输入发生变化会停止并要求重新检查。缓存只保存摘要;缺少锁文件、外部本地依赖、符号链接、文件过多或无法核对时执行完整检查。手工修改 node_modules 不属于受支持的依赖管理方式,应修改依赖声明并重新安装。
21
+ 每次检查都直接执行当前工作区声明的 check/test/build 脚本;构建工具和包管理器可以自行使用缓存。目标平台的权限、密钥和模型条件仍在正式检查和部署前预检。Devkit 不维护额外的工作区摘要凭据,也不会因为分支、提交或文档变化引入候选复用分支。
22
22
 
23
23
  成功检查返回 `sealedArtifact.state: check-did-not-seal`、`sealed: false`、`usableForDeploy: false`。旧 AppPackage 不是当前检查结果。要发布测试环境可直接运行 deploy,它已包含完整检查;不要连续重复执行 check、test 和 build。
24
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "2.14.0",
3
+ "version": "2.16.0",
4
4
  "description": "OpenXiangda 2.0 的统一命令、应用 SDK、MCP 与中文 AI 技能资料。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,13 +60,13 @@
60
60
  "antd-mobile": "5.42.3",
61
61
  "dayjs": "1.11.18",
62
62
  "docx-preview": "0.3.7",
63
- "openxiangda-cli": "2.4.3",
64
- "openxiangda-contracts": "2.12.0",
65
- "openxiangda-devkit-core": "2.10.0",
63
+ "openxiangda-cli": "2.4.5",
64
+ "openxiangda-contracts": "2.13.0",
65
+ "openxiangda-devkit-core": "2.12.0",
66
66
  "openxiangda-legacy": "npm:openxiangda@1.0.269",
67
- "openxiangda-mcp": "2.0.18",
68
- "openxiangda-nest": "2.3.3",
69
- "openxiangda-skill-kit": "2.1.2",
67
+ "openxiangda-mcp": "2.0.20",
68
+ "openxiangda-nest": "2.4.0",
69
+ "openxiangda-skill-kit": "2.1.4",
70
70
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
71
71
  },
72
72
  "peerDependencies": {
@@ -132,44 +132,44 @@
132
132
  },
133
133
  "openxiangdaRelease": {
134
134
  "schemaVersion": "openxiangda.release-notes/v1",
135
- "version": "2.14.0",
135
+ "version": "2.16.0",
136
136
  "status": "reviewed",
137
- "title": "OpenXiangda 2.14.0:匿名公开预约契约闭环",
138
- "summary": "为匿名公开数据读取和提交补齐固定过滤、服务端生成字段、跨资源预约规则校验与同路由多策略支持,并统一客户端、编译器和平台契约。",
137
+ "title": "OpenXiangda 2.16.0:相机图片直传与声明一次写对",
138
+ "summary": "浏览器托管上传按 maxPixels 契约预检并自动降采样超限图片,声明校验放宽可推导默认值、报错附带可复制片段,新增声明速查主题与事务快照/幂等冲突助手。",
139
139
  "newFeatures": [
140
- "匿名公开策略支持固定服务端过滤、服务端随机令牌生成和跨资源 schedule 校验;提交事务返回 generated 字段。",
141
- "同一用户路由可以按设备或资源声明多条匿名策略,浏览器客户端以策略码隔离 bootstrap 缓存并显式协商客户端契约版本。",
142
- "匿名公开客户端支持公开列表/详情读取、草稿提交回执和受控的文件、富文本及子表字段投影。"
140
+ "资源与匿名托管上传通道读取上传计划发布的 maxPixels,超限图片本地降采样后重新发起上传;旧平台无契约时回退 40MP 保护阈值。",
141
+ "openxiangda/nest 新增 optionSnapshot、userSnapshot、departmentSnapshot、resourceSnapshot 快照值助手与 isIdempotencyConflict 冲突判定,事务写快照字段不再手工拼形状。",
142
+ "新增 declarations-cheatsheet 中文主题(入口 Skill 已路由):按错误码到规则到正确片段组织全部隐式声明规则,并附两模型起步骨架。"
143
143
  ],
144
144
  "fixes": [
145
- "修复匿名公开策略把合法驼峰字段名(例如 visitDate、openAt、qrToken)按全小写稳定 code 错误拒绝的问题;JSON Schema、Devkit 和原生编译器现在使用统一字段 code 规则。",
146
- "平台服务端强制应用公开过滤,匿名草稿过期时清理关联文件,并在缺少客户端版本声明时收窄 source hosting OAuth 能力。"
145
+ "number.integer 允许可选 precision 位数并拒绝 scale 小数位;number.decimal 精度报错附带合法示例,resource-ref 缺 source、平台保留能力与 AI 写操作副作用报错的补救文本直接携带正确片段。",
146
+ "audit.read 支持 true 声明糖自动绑定资源读能力;CRUD 视图 list/form/detail 省略 model 时继承视图模型。",
147
+ "业务验收报告性能条目的报错逐字段定位,指出缺失字段名与最少实义字符要求。"
147
148
  ],
148
149
  "affectedUsers": [
149
- "需要在没有平台账号的情况下读取公开数据或提交草稿的 OpenXiangda 2.0 应用开发者。",
150
- "使用匿名预约、服务端生成核验令牌或跨资源提交校验的应用,以及部署匹配平台服务端的运维人员。"
150
+ "所有在表单中上传手机照片或长截图的 OpenXiangda 2.0 应用用户与开发者。",
151
+ "首次编写 openxiangda.config.ts 声明、在 Nest 事务中写 option/user/resource-ref 字段的开发者与 AI 工作流。"
151
152
  ],
152
153
  "upgradeSteps": [
153
- "先部署包含匿名公开固定过滤、生成字段和 schedule 校验的匹配平台组合,再将应用精确依赖升级到 openxiangda 2.14.0",
154
- "刷新项目 Skill 和工作区指引,重新生成契约并运行项目 check、test、build;匿名策略中的字段引用可继续使用合法驼峰命名。",
155
- "在测试环境验证跨浏览器草稿隔离、幂等提交、停用前置资源拒绝和管理员核验,再使用同一测试版本晋级生产。"
154
+ "应用精确依赖升级到 openxiangda 2.16.0 并刷新项目 Skill;普通 CRUD 与既有声明无需修改。",
155
+ "新的图片字段上传自动获得像素预检;配合已修复 maxPixels 契约的平台(100MP 上限)效果最佳,旧平台自动回退压缩保护。",
156
+ "后端事务代码可选用新的快照值助手与 isIdempotencyConflict 复核模式,参见 backend 文档快照写入与幂等冲突两节。"
156
157
  ],
157
158
  "knownLimitations": [
158
- "schedule 首版只表达有界的预约窗口复核,不提供容量配额、取消改期、通知或审批流程;这些能力需要单独的应用变更评审。",
159
- "匿名公开接口仍只接受策略声明的固定过滤和字段投影,不能由调用方动态追加筛选、排序或投影。",
160
- "发行包和平台部署检查不能替代目标环境的真实角色、浏览器和性能验收。"
159
+ "前端压缩依赖浏览器 createImageBitmap;极旧的浏览器跳过预检,由服务端明确错误兜底。",
160
+ "操作通道(operation-managed)上传不自动改写业务文件内容,超限时返回带尺寸的 PIXEL_LIMIT_EXCEEDED,由调用方自行压缩。"
161
161
  ],
162
162
  "compatibility": {
163
163
  "node": ">=24",
164
164
  "workspaceGenerations": [
165
165
  "v2"
166
166
  ],
167
- "platform": "需要匹配 OpenXiangda 2.0 匿名公开策略、固定过滤、服务端生成字段和 schedule 校验的服务端契约;V1 引擎保持独立。",
167
+ "platform": "maxPixels 契约与 100MP 处理上限需要配套平台版本;旧平台通过 40MP 回退阈值继续工作。V1 引擎保持独立。",
168
168
  "v1": "不改变 V1 运行时或应用。"
169
169
  },
170
170
  "issues": [],
171
- "sha256": "5b2dfbcf66a0e14d8619914b1d19123f84d35640978ffac4a6c2bef3e8deef8a",
172
- "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.14.0"
171
+ "sha256": "55687eb0915b5a1d2743f4b3c5aa54d7a3dbfad91b467cc9f80be10995aa83f5",
172
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.16.0"
173
173
  },
174
174
  "scripts": {
175
175
  "build": "node ../../scripts/prune-package-dist.mjs && tsc -p tsconfig.json && node scripts/copy-assets.mjs",
@@ -0,0 +1,40 @@
1
+ {
2
+ "schemaVersion": "openxiangda.release-notes/v1",
3
+ "version": "2.15.0",
4
+ "status": "reviewed",
5
+ "title": "OpenXiangda 2.15.0:CRUD 复用 Data API 引导与 Nest 路由门禁",
6
+ "summary": "为普通 CRUD 补齐浏览器端 Data API 正面路径文档与 Nest 启用决策表,并在 check/dev 拒绝未绑定已声明 operation 的应用 controller 路由,使数据增删改查默认回到平台统一接口。",
7
+ "newFeatures": [
8
+ "开发资料新增「自定义页面消费平台数据」:createNativeResourceClient 的列表/详情/写入/删除/上传用法、expectedRevision 乐观锁、transactNativeData 幂等事务与 batchAggregateNativeResources 服务端聚合。",
9
+ "需求与开发流程新增「判定是否真的需要 Nest 后端」决策表,工作区 AGENTS 模板与按需后端资料同步指向;只有真实外部副作用或无法声明的跨资源不变量才启用 Nest。",
10
+ "devkit check/dev 新增应用 controller operation 门禁:未以 `@OpenXiangdaOperation(appOperations.具名操作)` 绑定已声明 operation 的路由返回 `OPENXIANGDA_NEST_CONTROLLER_OPERATION_REQUIRED`,手写契约字面量返回 `OPENXIANGDA_NEST_OPERATION_CONTRACT_MUST_BE_DECLARED`。"
11
+ ],
12
+ "fixes": [
13
+ "修复交付流程简化后 CLI 黑盒仍期望已删除的 DELIVERY_GIT_COMMIT_REQUIRED 的问题;黑盒改为断言 AppSpec 章节门禁,verify:affected 恢复可用。"
14
+ ],
15
+ "affectedUsers": [
16
+ "所有 OpenXiangda 2.0 应用开发者和 AI 辅助开发会话;新指引让列表、表单、详情、删除优先复用平台 Data API 与事务守卫。",
17
+ "维护含自写 Nest controller 的既有应用:升级后 check 会要求绑定已声明 operation 或移除该路由。"
18
+ ],
19
+ "upgradeSteps": [
20
+ "将应用精确依赖升级到 openxiangda 2.15.0,刷新项目 Skill 与工作区指引后运行 check。",
21
+ "对报 `OPENXIANGDA_NEST_CONTROLLER_OPERATION_REQUIRED` 的路由:普通 CRUD 改用 createNativeResourceClient 或标准 CRUD 页面;真实业务动作先在 openxiangda.config.ts 声明 operation(capability kind: 'backend'),再绑定 `@OpenXiangdaOperation(appOperations.具名操作)`。",
22
+ "按 development#backend-decision 决策表复查既有后端使用面,完成 check、test、build 后按常规测试发布流程验证。"
23
+ ],
24
+ "knownLimitations": [
25
+ "门禁是静态判定:已声明 operation 但方法体仍只转发普通 CRUD 的 controller 不会被拒绝;该层由设计评审与后续 AppSpec 能力复用清单兜底。",
26
+ "类级别 @OpenXiangdaOperation 绑定不被接受,需要逐路由方法绑定同一生成契约。",
27
+ "文档与门禁不替代目标环境的真实角色、浏览器和性能验收。"
28
+ ],
29
+ "compatibility": {
30
+ "node": ">=24",
31
+ "workspaceGenerations": [
32
+ "v2"
33
+ ],
34
+ "platform": "不要求平台服务端变更;已有应用升级后 check 行为变化见升级步骤。",
35
+ "v1": "不改变 V1 运行时或应用。"
36
+ },
37
+ "issues": [],
38
+ "sha256": "45fe1d3ad3b0a04d8ec4b3f657a201dee14823576fe8b48a369a92f7d0c9e029",
39
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.15.0"
40
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "schemaVersion": "openxiangda.release-notes/v1",
3
+ "version": "2.16.0",
4
+ "status": "reviewed",
5
+ "title": "OpenXiangda 2.16.0:相机图片直传与声明一次写对",
6
+ "summary": "浏览器托管上传按 maxPixels 契约预检并自动降采样超限图片,声明校验放宽可推导默认值、报错附带可复制片段,新增声明速查主题与事务快照/幂等冲突助手。",
7
+ "newFeatures": [
8
+ "资源与匿名托管上传通道读取上传计划发布的 maxPixels,超限图片本地降采样后重新发起上传;旧平台无契约时回退 40MP 保护阈值。",
9
+ "openxiangda/nest 新增 optionSnapshot、userSnapshot、departmentSnapshot、resourceSnapshot 快照值助手与 isIdempotencyConflict 冲突判定,事务写快照字段不再手工拼形状。",
10
+ "新增 declarations-cheatsheet 中文主题(入口 Skill 已路由):按错误码到规则到正确片段组织全部隐式声明规则,并附两模型起步骨架。"
11
+ ],
12
+ "fixes": [
13
+ "number.integer 允许可选 precision 位数并拒绝 scale 小数位;number.decimal 精度报错附带合法示例,resource-ref 缺 source、平台保留能力与 AI 写操作副作用报错的补救文本直接携带正确片段。",
14
+ "audit.read 支持 true 声明糖自动绑定资源读能力;CRUD 视图 list/form/detail 省略 model 时继承视图模型。",
15
+ "业务验收报告性能条目的报错逐字段定位,指出缺失字段名与最少实义字符要求。"
16
+ ],
17
+ "affectedUsers": [
18
+ "所有在表单中上传手机照片或长截图的 OpenXiangda 2.0 应用用户与开发者。",
19
+ "首次编写 openxiangda.config.ts 声明、在 Nest 事务中写 option/user/resource-ref 字段的开发者与 AI 工作流。"
20
+ ],
21
+ "upgradeSteps": [
22
+ "应用精确依赖升级到 openxiangda 2.16.0 并刷新项目 Skill;普通 CRUD 与既有声明无需修改。",
23
+ "新的图片字段上传自动获得像素预检;配合已修复 maxPixels 契约的平台(100MP 上限)效果最佳,旧平台自动回退压缩保护。",
24
+ "后端事务代码可选用新的快照值助手与 isIdempotencyConflict 复核模式,参见 backend 文档快照写入与幂等冲突两节。"
25
+ ],
26
+ "knownLimitations": [
27
+ "前端压缩依赖浏览器 createImageBitmap;极旧的浏览器跳过预检,由服务端明确错误兜底。",
28
+ "操作通道(operation-managed)上传不自动改写业务文件内容,超限时返回带尺寸的 PIXEL_LIMIT_EXCEEDED,由调用方自行压缩。"
29
+ ],
30
+ "compatibility": {
31
+ "node": ">=24",
32
+ "workspaceGenerations": [
33
+ "v2"
34
+ ],
35
+ "platform": "maxPixels 契约与 100MP 处理上限需要配套平台版本;旧平台通过 40MP 回退阈值继续工作。V1 引擎保持独立。",
36
+ "v1": "不改变 V1 运行时或应用。"
37
+ },
38
+ "issues": [],
39
+ "sha256": "55687eb0915b5a1d2743f4b3c5aa54d7a3dbfad91b467cc9f80be10995aa83f5",
40
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.16.0"
41
+ }
@@ -4,7 +4,7 @@
4
4
  {
5
5
  "name": "openxiangda-v2",
6
6
  "description": "使用 OpenXiangda 2.0 从模糊业务想法、已有资料或具体变更出发,通过对话发现模块、完成详细产品设计,由 AI 在工作区内调用 OpenDesign 原版 CLI/Skill/MCP 形成整体视觉与可运行原型,再开发、检查和交付应用。OpenDesign 客户端只作为可选预览器;维护 1.x 应用时使用对应的 1.x 技能。",
7
- "sha256": "a00a3d24f14816709034371caca705ada303e4e1027b1d7c3019276920eef6ea"
7
+ "sha256": "642c56742434479335e81cd6c285cd810baaa247b72f79e11536912d307609e0"
8
8
  }
9
9
  ]
10
10
  }