openxiangda 2.15.0 → 2.17.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.
Files changed (36) hide show
  1. package/dist/browser/application.d.ts.map +1 -1
  2. package/dist/browser/application.js +13 -0
  3. package/dist/browser/application.js.map +1 -1
  4. package/dist/browser/components/platform-fields/resource-query.d.ts +2 -0
  5. package/dist/browser/components/platform-fields/resource-query.d.ts.map +1 -1
  6. package/dist/browser/components/platform-fields/resource-query.js +3 -0
  7. package/dist/browser/components/platform-fields/resource-query.js.map +1 -1
  8. package/dist/browser/components/resource/StandardUserResourcePages.d.ts +36 -0
  9. package/dist/browser/components/resource/StandardUserResourcePages.d.ts.map +1 -0
  10. package/dist/browser/components/resource/StandardUserResourcePages.js +142 -0
  11. package/dist/browser/components/resource/StandardUserResourcePages.js.map +1 -0
  12. package/dist/browser/components/resource/useResourceFormDrafts.d.ts.map +1 -1
  13. package/dist/browser/platform-client.d.ts.map +1 -1
  14. package/dist/browser/platform-client.js +100 -43
  15. package/dist/browser/platform-client.js.map +1 -1
  16. package/dist/browser/route-manifest.d.ts.map +1 -1
  17. package/dist/browser/route-manifest.js +22 -8
  18. package/dist/browser/route-manifest.js.map +1 -1
  19. package/dist/nest.d.ts +1 -0
  20. package/dist/nest.d.ts.map +1 -1
  21. package/dist/nest.js +1 -0
  22. package/dist/nest.js.map +1 -1
  23. package/documentation/backend.md +35 -0
  24. package/documentation/declarations-cheatsheet.md +191 -0
  25. package/documentation/frontend.md +16 -1
  26. package/documentation/getting-started.md +7 -7
  27. package/documentation/manifest.json +10 -4
  28. package/package.json +24 -25
  29. package/releases/2.16.0.json +41 -0
  30. package/releases/2.17.0.json +39 -0
  31. package/skills/manifest.json +1 -1
  32. package/skills/openxiangda-v2/SKILL.md +5 -4
  33. package/skills/openxiangda-v2/references/backend.md +35 -0
  34. package/skills/openxiangda-v2/references/declarations-cheatsheet.md +191 -0
  35. package/skills/openxiangda-v2/references/frontend.md +16 -1
  36. package/skills/openxiangda-v2/references/getting-started.md +7 -7
@@ -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,191 @@
1
+ # 声明速查:一次写对 openxiangda.config.ts {#cheatsheet}
2
+
3
+ 按"错误码 → 规则 → 正确片段"组织。这些规则全部来自真实返工:先扫一遍本页,再写声明,能省掉绝大多数首轮校验迭代。普通 CRUD 的完整可过检骨架见文末。
4
+
5
+ ## 模块与 CRUD 视图
6
+
7
+ | 规则 | 正确写法 |
8
+ | --- | --- |
9
+ | `crud[].model` 必须引用本模块已声明的模型 | `crud: [{ model: 'repair-requests', ... }]` |
10
+ | `user: true` 一行声明即可生成用户端标准面(“我的记录”列表 + 提交表单,双端),并自动成为登录落地页 | `crud: [{ model: 'supply-requests', user: true, ... }]`;多资源时 `user: { home: true }` 指定落地资源,“仅本人”是展示过滤,行级隔离仍用 dataPolicies |
11
+ | 视图 `list`/`form`/`detail` 的 `model` 可省略(继承视图模型);显式声明时必须与 `crud[].model` 一致 | `list: { fields: [...] }` 即可,不必写 `model` |
12
+ | 每个模型最多 20 个命名视图;命名视图需要稳定 `code` + `name` | `crud: [{ model: 'x', code: 'x-active', name: '进行中', ... }]` |
13
+ | 新建视图的 `form.fields` 必须覆盖全部无默认必填字段,或显式 `generated.create: false` | 检查器会列出缺失字段 |
14
+
15
+ ## 字段声明
16
+
17
+ | 规则 | 正确片段 |
18
+ | --- | --- |
19
+ | `option.*` 字段的值是 `{ label, value }` 快照(比较键是 `value`) | `options: [{ label: '教学设备', value: 'teaching' }]` |
20
+ | `number.integer` 可省略精度;只允许 `precision`(位数),不允许 `scale` | `{ code: 'qty', type: 'number.integer' }` |
21
+ | `number.decimal` 的 `precision` 必填、`scale` 0 到 precision | `{ type: 'number.decimal', precision: 12, scale: 2 }` |
22
+ | `audit.read` 可写 `true`(绑定本资源读能力)或能力数组 | `audit: { read: true }` |
23
+ | `resource-ref.*` 必须带 `source` 来源协议 | `{ type: 'resource-ref.single', source: { kind: 'resource', resourceCode: 'repair-requests', labelField: 'title', searchFields: ['title'], pageSize: 20, loadMode: 'search' } }` |
24
+ | `labelField` 必须指向目标资源的 `text.short` / `text.long` 字段 | 不要用流水号/选项字段当 label |
25
+ | 每个字段都必须带中文/业务 `label`(含子表外键与排序字段) | `{ code: 'requestId', type: 'uuid', label: '所属申请', required: true }` |
26
+ | 子表 `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 }` |
27
+ | 图片/附件的 `file` 限定数量与大小 | `file: { maxCount: 3, maxSizeMb: 10, accept: ['image/png', 'image/jpeg'] }` |
28
+
29
+ ## 权限声明
30
+
31
+ | 规则 | 正确片段 |
32
+ | --- | --- |
33
+ | 平台保留能力(如 `app:<app>:directory:read`)**不能**在 `capabilities` 里重复声明,直接在角色中引用即可 | `const directoryRead = \`app:\${APP_CODE}:directory:read\`` → `roles: [{ code: 'admin', capabilities: [directoryRead] }]` |
34
+ | 资源 CRUD 能力码用 `resourceCapabilityCodes(appCode, resourceCode)` 生成 | `const crud = resourceCapabilityCodes(APP_CODE, 'repair-requests')` → `capabilities: [crud.read, crud.create]` |
35
+ | `authenticatedUserRoleCode` 是平台登录用户的基线角色 | `authz: { authenticatedUserRoleCode: 'app-user', ... }` |
36
+
37
+ ## 后端操作(Nest)
38
+
39
+ | 规则 | 正确片段 |
40
+ | --- | --- |
41
+ | 写操作的 `ai.sideEffects` 至少一条具体副作用 | `ai: { name: '受理派单', ..., risk: 'write', sideEffects: ['更新报修单状态为处理中', '写入一条派工记录'] }` |
42
+ | GET 操作的 `ai.risk` 只能是 `read`;写操作不能是 `read` | `risk: 'read'` ↔ `method: 'GET'` |
43
+ | controller 路由必须绑定 `@OpenXiangdaOperation(appOperations.<code>)`,普通 CRUD 不写 controller | check 门禁会拒绝未绑定路由 |
44
+ | 事务守卫 `errorCode` 必须匹配 `^OPENXIANGDA_[A-Z0-9_]{1,96}$` | `errorCode: 'OPENXIANGDA_REPAIR_REQUEST_NOT_PENDING'` |
45
+
46
+ ## 事务写入快照字段
47
+
48
+ option / user / department / resource-ref 字段在事务和普通写入里都必须写快照对象,不能写裸字符串:
49
+
50
+ ```ts
51
+ import { optionSnapshot, userSnapshot, resourceSnapshot } from 'openxiangda/nest';
52
+
53
+ await this.data.transaction(idempotentTransaction(input.idempotencyKey, [
54
+ {
55
+ operation: 'update',
56
+ resourceCode: 'repair-requests',
57
+ id: input.requestId,
58
+ expectedRevision: revision,
59
+ data: {
60
+ status: optionSnapshot('处理中', 'processing'), // 不是 'processing'
61
+ assignedTechnician: userSnapshot(input.technicianId), // 不是裸 userId
62
+ },
63
+ },
64
+ {
65
+ operation: 'create',
66
+ resourceCode: 'repair-assignments',
67
+ data: {
68
+ requestId: resourceSnapshot('repair-requests', input.requestId, requestTitle),
69
+ technician: userSnapshot(input.technicianId),
70
+ },
71
+ },
72
+ ]));
73
+ ```
74
+
75
+ 读取时状态判断用 `record.data.status?.value === 'pending'`(存储值是快照对象)。
76
+
77
+ ## 幂等冲突复核模式
78
+
79
+ update 事务必须携带 `expectedRevision`;重试时 revision 已前进会让同一幂等键的内容指纹漂移,平台返回 409 `OPENXIANGDA_NATIVE_DATA_IDEMPOTENCY_CONFLICT`(而非 `replayed: true`)。标准处理:
80
+
81
+ ```ts
82
+ import { isIdempotencyConflict } from 'openxiangda/nest';
83
+
84
+ try {
85
+ result = await this.data.transaction(idempotentTransaction(key, operations, guards));
86
+ } catch (error) {
87
+ if (isIdempotencyConflict(error)) {
88
+ const current = await this.data.get('repair-requests', input.requestId);
89
+ if (/* 状态已离开 pending,说明本键的效果已生效 */) {
90
+ return { idempotencyKey: key, replayed: true, ... 当前状态 };
91
+ }
92
+ }
93
+ throw error;
94
+ }
95
+ ```
96
+
97
+ 不要用新生成的幂等键重试冲突——那会绕过幂等保护重复执行业务动作。
98
+
99
+ ## 两模型起步骨架(可直接改造)
100
+
101
+ ```ts
102
+ import {
103
+ adminNavigationGroup, adminResourcePage, defineAdminNavigation,
104
+ defineApplicationModule, defineOpenXiangdaApp, resourceCapabilityCodes,
105
+ } from 'openxiangda/config';
106
+
107
+ const APP_CODE = 'my-app';
108
+ const requestCrud = resourceCapabilityCodes(APP_CODE, 'requests');
109
+
110
+ const requests = {
111
+ code: 'requests', name: '申请单',
112
+ audit: { read: true },
113
+ fields: [
114
+ { code: 'title', type: 'text.short', label: '标题', required: true },
115
+ { code: 'category', type: 'option.single', label: '类别', required: true,
116
+ options: [{ label: '普通', value: 'normal' }, { label: '紧急', value: 'urgent' }] },
117
+ { code: 'status', type: 'option.single', label: '状态', required: true,
118
+ options: [{ label: '待受理', value: 'pending' }, { label: '已完成', value: 'done' }] },
119
+ { code: 'photo', type: 'image', label: '照片', file: { maxCount: 3, maxSizeMb: 10 } },
120
+ ],
121
+ };
122
+ const items = {
123
+ code: 'request-items', name: '明细',
124
+ fields: [
125
+ // 所有字段(含子表外键/排序)都必须声明中文 label。
126
+ { code: 'requestId', type: 'uuid', label: '所属申请', required: true },
127
+ { code: 'sortOrder', type: 'number.integer', label: '排序', required: true },
128
+ { code: 'name', type: 'text.short', label: '名称', required: true },
129
+ { code: 'qty', type: 'number.integer', label: '数量' },
130
+ { code: 'price', type: 'number.decimal', label: '单价', precision: 12, scale: 2 },
131
+ ],
132
+ };
133
+
134
+ export default defineOpenXiangdaApp({
135
+ app: { code: APP_CODE, name: '我的应用' },
136
+ frontend: {
137
+ root: 'apps/web',
138
+ devicePolicy: { kind: 'viewport-family', mobileMaxWidthPx: 900, desktopMinWidthPx: 901 },
139
+ routes: [
140
+ { code: 'application-home', path: '/home', label: '应用首页', surface: 'user' },
141
+ { code: 'application-home-mobile', path: '/m/home', label: '移动首页', surface: 'user' },
142
+ ],
143
+ authentication: {
144
+ accountMode: 'existing-platform-users-only',
145
+ registration: { mode: 'reject' },
146
+ methods: [{ code: 'password', type: 'password', label: '账号密码登录', presentation: 'primary', required: true }],
147
+ surfaces: {
148
+ desktop: { routeCode: 'application-login', path: '/login', defaultRouteCode: 'application-home' },
149
+ mobile: { routeCode: 'application-login-mobile', path: '/m/login', defaultRouteCode: 'application-home-mobile' },
150
+ },
151
+ },
152
+ admin: {
153
+ navigation: defineAdminNavigation([
154
+ adminNavigationGroup('main', '业务管理', [adminResourcePage('requests', { label: '申请单' })], { icon: 'database', order: 100 }),
155
+ ]),
156
+ },
157
+ },
158
+ modules: [defineApplicationModule({
159
+ code: 'main',
160
+ models: [requests, items],
161
+ crud: [
162
+ {
163
+ model: 'requests',
164
+ list: {
165
+ fields: ['title', 'category', 'status'],
166
+ filterFields: ['category', 'status'],
167
+ searchableFields: ['title'],
168
+ defaultPageSize: 20,
169
+ defaultSort: { field: 'title', order: 'asc' },
170
+ },
171
+ mobile: { enabled: true },
172
+ },
173
+ ],
174
+ })],
175
+ authz: {
176
+ authenticatedUserRoleCode: 'app-user',
177
+ capabilities: [],
178
+ roles: [
179
+ { code: 'app-user', name: '应用用户', capabilities: [requestCrud.read, requestCrud.create] },
180
+ { code: 'admin', name: '管理员', capabilities: [requestCrud.read, requestCrud.create, requestCrud.update, requestCrud.delete] },
181
+ ],
182
+ scopeDimensions: [], scopeSources: [], dataPolicies: [], authorizationTransitions: [],
183
+ },
184
+ });
185
+ ```
186
+
187
+ `request-items` 不出现在 `crud` 里:子表行随 `requests` 表单的 `subtable` 字段写入(在 `requests.fields` 里补 `{ code: 'items', type: 'subtable', subtable: { resourceCode: 'request-items', foreignKey: 'requestId', orderField: 'sortOrder', maxRows: 20 } }`)。
188
+
189
+ ## 图片上传的像素上限
190
+
191
+ image/signature/富文本图片字段在上传计划(initiate)里返回 `maxPixels`;超过上限的图片会被标准组件自动压缩后重新发起上传。用 API 直传时自行按 `maxPixels` 预检。当前平台上限覆盖主流手机主摄(48/50/64MP);超出会得到带实际尺寸的 `OPENXIANGDA_NATIVE_DATA_IMAGE_PIXEL_LIMIT_EXCEEDED` 错误。
@@ -63,7 +63,22 @@ capability、应用角色和数据策略。前端只根据当前登录用户完
63
63
  可替换 Data API adapter。不要调用自定义 Nest CRUD、Function 或 Workflow 来绕过
64
64
  Data API。只有真正需要事务或外部系统的动作才使用同源 `/api`。
65
65
 
66
- ### 自定义页面消费平台数据 {#data-access}
66
+ ### 生成式用户标准面 {#user-surface}
67
+
68
+ 对普通用户(登录基线角色)开放提交的 CRUD 资源,在 CRUD 视图上声明 `user: true` 即可获得
69
+ 开箱即用的用户端标准页,无需编写任何 React 代码:
70
+
71
+ - “我的记录”列表(`/my/<resource>`,移动端 `/m/my/<resource>`):默认仅显示当前登录用户
72
+ 创建的记录(`created_by` 展示过滤),支持分页、行详情抽屉与“提交”入口。
73
+ - “提交”表单(`/my/<resource>/submit`):复用标准字段渲染与文件上传,成功后回到列表。
74
+ - 首页接线:首个启用资源自动成为登录落地页与根路径;多资源时用 `user: { home: true }` 显式指定;
75
+ 声明的 `/home` 占位路由保留可用,也可以继续用自定义页面覆盖用户端体验。
76
+ - 能力接线自动完成:列表要求资源 read,提交要求 create;无能力的用户看到标准 403 面。
77
+
78
+ 注意:“仅本人”是页面展示过滤,不是授权边界。需要服务端强制行级隔离时按[数据与权限](./data-authz.md)
79
+ 声明 dataPolicies。
80
+
81
+ ## 自定义页面消费平台数据 {#data-access}
67
82
 
68
83
  自定义报表、工具页和用户端页面从 `openxiangda/core` 导入 `createNativeResourceClient`,
69
84
  用生成契约里的 surface 直接获得该资源的权威读写客户端;行、字段与操作授权由平台在
@@ -66,10 +66,10 @@ MCP 服务随项目根包一起安装,AI 客户端的 stdio 连接仍需配置
66
66
  以下命令的版本占位符由随包资料替换为该根包的精确版本。网站源码阅读者应先确认要使用的发行版本。
67
67
 
68
68
  ```bash
69
- pnpm dlx openxiangda@2.15.0 skill install --force
70
- pnpm dlx openxiangda@2.15.0 auth status --base-url <平台地址> --json
71
- pnpm dlx openxiangda@2.15.0 login --cwd my-app --base-url https://platform.example.com
72
- pnpm dlx openxiangda@2.15.0 create my-app --base-url https://platform.example.com
69
+ pnpm dlx openxiangda@2.17.0 skill install --force
70
+ pnpm dlx openxiangda@2.17.0 auth status --base-url <平台地址> --json
71
+ pnpm dlx openxiangda@2.17.0 login --cwd my-app --base-url https://platform.example.com
72
+ pnpm dlx openxiangda@2.17.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.15.0 auth status --base-url <平台> --json
175
- pnpm dlx openxiangda@2.15.0 source resolve <仓库URL> --base-url <平台> --json
176
- pnpm dlx openxiangda@2.15.0 source clone <仓库URL> <新目录> --base-url <平台> --json
174
+ pnpm dlx openxiangda@2.17.0 auth status --base-url <平台> --json
175
+ pnpm dlx openxiangda@2.17.0 source resolve <仓库URL> --base-url <平台> --json
176
+ pnpm dlx openxiangda@2.17.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.15.0",
3
+ "version": "2.17.0",
4
4
  "topics": [
5
5
  {
6
6
  "id": "getting-started",
7
7
  "title": "安装与开始开发",
8
8
  "file": "getting-started.md",
9
- "sha256": "8ce8ca74bff94aa582c4bce708a0d25529478c5062ed67ea7fb5227927d4fcd5"
9
+ "sha256": "55ee60630343fc27237a4f23737225811fdd1a62cece7fe6381df2508c585f91"
10
10
  },
11
11
  {
12
12
  "id": "product-design",
@@ -44,6 +44,12 @@
44
44
  "file": "development.md",
45
45
  "sha256": "c172cf5a6a20340b4142a1443b653d3e312f8a4464f990e69055046033011366"
46
46
  },
47
+ {
48
+ "id": "declarations-cheatsheet",
49
+ "title": "声明速查:一次写对 config",
50
+ "file": "declarations-cheatsheet.md",
51
+ "sha256": "f2eb26553e1e813e7fa0e42be447f41cd073953f7fd807e67d5308f4bfa9cf37"
52
+ },
47
53
  {
48
54
  "id": "application-foundation",
49
55
  "title": "业务模型与标准 CRUD",
@@ -66,7 +72,7 @@
66
72
  "id": "frontend",
67
73
  "title": "页面与标准组件扩展",
68
74
  "file": "frontend.md",
69
- "sha256": "61eedc337b1e64d404f5521eedf3ed6e3afc7ac341a4c56972ffe1000bb9bfd4"
75
+ "sha256": "30e30cde40a6c52ba6622770f0df73d717e395dca1fc916c198bca26756cb099"
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": "f934afc1b7db45dca107de0088db5573005fcc7601b12f15c0d049a5c82feaae"
105
+ "sha256": "75c8f9b83c37afe53b57d0283fa814a5153aa7d72a85efb2a1d9c4a9c1844e6a"
100
106
  },
101
107
  {
102
108
  "id": "administration",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "2.15.0",
3
+ "version": "2.17.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.4",
64
- "openxiangda-contracts": "2.12.0",
65
- "openxiangda-devkit-core": "2.11.0",
63
+ "openxiangda-cli": "2.4.6",
64
+ "openxiangda-contracts": "2.14.0",
65
+ "openxiangda-devkit-core": "2.13.0",
66
66
  "openxiangda-legacy": "npm:openxiangda@1.0.269",
67
- "openxiangda-mcp": "2.0.19",
68
- "openxiangda-nest": "2.3.3",
69
- "openxiangda-skill-kit": "2.1.3",
67
+ "openxiangda-mcp": "2.0.21",
68
+ "openxiangda-nest": "2.4.1",
69
+ "openxiangda-skill-kit": "2.1.5",
70
70
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
71
71
  },
72
72
  "peerDependencies": {
@@ -132,43 +132,42 @@
132
132
  },
133
133
  "openxiangdaRelease": {
134
134
  "schemaVersion": "openxiangda.release-notes/v1",
135
- "version": "2.15.0",
135
+ "version": "2.17.0",
136
136
  "status": "reviewed",
137
- "title": "OpenXiangda 2.15.0:CRUD 复用 Data API 引导与 Nest 路由门禁",
138
- "summary": "为普通 CRUD 补齐浏览器端 Data API 正面路径文档与 Nest 启用决策表,并在 check/dev 拒绝未绑定已声明 operation 的应用 controller 路由,使数据增删改查默认回到平台统一接口。",
137
+ "title": "OpenXiangda 2.17.0:一行声明生成用户端标准面",
138
+ "summary": "CRUD 视图声明 user: true 即可为普通用户生成“我的记录 + 提交”标准页面(桌面与移动双端),自动接线能力与登录落地,简单 CRUD 应用开箱即有完整用户端闭环。",
139
139
  "newFeatures": [
140
- "开发资料新增「自定义页面消费平台数据」:createNativeResourceClient 的列表/详情/写入/删除/上传用法、expectedRevision 乐观锁、transactNativeData 幂等事务与 batchAggregateNativeResources 服务端聚合。",
141
- "需求与开发流程新增「判定是否真的需要 Nest 后端」决策表,工作区 AGENTS 模板与按需后端资料同步指向;只有真实外部副作用或无法声明的跨资源不变量才启用 Nest。",
142
- "devkit check/dev 新增应用 controller operation 门禁:未以 `@OpenXiangdaOperation(appOperations.具名操作)` 绑定已声明 operation 的路由返回 `OPENXIANGDA_NEST_CONTROLLER_OPERATION_REQUIRED`,手写契约字面量返回 `OPENXIANGDA_NEST_OPERATION_CONTRACT_MUST_BE_DECLARED`。"
140
+ "resource-records / resource-submit 两种用户标准路由:`/my/<资源代码>` 列表与 `/my/<资源代码>/submit` 提交(移动端 `/m/` 前缀),能力分别绑定资源 read / create。",
141
+ "“我的记录”标准组件:默认仅展示当前登录用户创建的记录(created_by 过滤)、分页、行详情抽屉与提交入口;提交页复用标准生成表单。",
142
+ "首个启用资源自动成为登录落地页与根路径;多资源用 user: { home: true } 显式指定;声明式 /home 占位仍可覆盖。"
143
143
  ],
144
144
  "fixes": [
145
- "修复交付流程简化后 CLI 黑盒仍期望已删除的 DELIVERY_GIT_COMMIT_REQUIRED 的问题;黑盒改为断言 AppSpec 章节门禁,verify:affected 恢复可用。"
145
+ "GenericResourceQuery 新增显式 createdBy 过滤映射 created_by 系统字段,不放宽其他系统字段查询。"
146
146
  ],
147
147
  "affectedUsers": [
148
- "所有 OpenXiangda 2.0 应用开发者和 AI 辅助开发会话;新指引让列表、表单、详情、删除优先复用平台 Data API 与事务守卫。",
149
- "维护含自写 Nest controller 的既有应用:升级后 check 会要求绑定已声明 operation 或移除该路由。"
148
+ "为普通登录用户提供提交与查询的简单 CRUD 应用开发者。",
149
+ "使用 openxiangda 2.16 及以下的应用升级后行为零变化(未声明 user 面不受影响)。"
150
150
  ],
151
151
  "upgradeSteps": [
152
- "将应用精确依赖升级到 openxiangda 2.15.0,刷新项目 Skill 与工作区指引后运行 check。",
153
- "对报 `OPENXIANGDA_NEST_CONTROLLER_OPERATION_REQUIRED` 的路由:普通 CRUD 改用 createNativeResourceClient 或标准 CRUD 页面;真实业务动作先在 openxiangda.config.ts 声明 operation(capability kind: 'backend'),再绑定 `@OpenXiangdaOperation(appOperations.具名操作)`。",
154
- " development#backend-decision 决策表复查既有后端使用面,完成 check、test、build 后按常规测试发布流程验证。"
152
+ "应用精确依赖升级到 openxiangda 2.17.0 并刷新项目 Skill。",
153
+ "在需要用户端的 CRUD 视图上追加 user: true;如需自定义用户端体验,继续用自定义页面覆盖即可。",
154
+ "行级强隔离仍按数据与权限文档声明 dataPolicies;“仅本人”列表是展示过滤。"
155
155
  ],
156
156
  "knownLimitations": [
157
- "门禁是静态判定:已声明 operation 但方法体仍只转发普通 CRUD 的 controller 不会被拒绝;该层由设计评审与后续 AppSpec 能力复用清单兜底。",
158
- "类级别 @OpenXiangdaOperation 绑定不被接受,需要逐路由方法绑定同一生成契约。",
159
- "文档与门禁不替代目标环境的真实角色、浏览器和性能验收。"
157
+ "“我的记录”的本人过滤是查询层展示过滤,非服务端授权边界。",
158
+ "详情抽屉按声明字段只读渲染,暂不支持在用户端编辑已有记录(可继续用自定义页面)。"
160
159
  ],
161
160
  "compatibility": {
162
161
  "node": ">=24",
163
162
  "workspaceGenerations": [
164
163
  "v2"
165
164
  ],
166
- "platform": "不要求平台服务端变更;已有应用升级后 check 行为变化见升级步骤。",
165
+ "platform": "无需平台配合变更;路由与能力契约向后兼容。V1 引擎保持独立。",
167
166
  "v1": "不改变 V1 运行时或应用。"
168
167
  },
169
168
  "issues": [],
170
- "sha256": "45fe1d3ad3b0a04d8ec4b3f657a201dee14823576fe8b48a369a92f7d0c9e029",
171
- "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.15.0"
169
+ "sha256": "5656c1f523a25d37770bed992d352dc372f3e08f5ce8ca25394443d448d25870",
170
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.17.0"
172
171
  },
173
172
  "scripts": {
174
173
  "build": "node ../../scripts/prune-package-dist.mjs && tsc -p tsconfig.json && node scripts/copy-assets.mjs",
@@ -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
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "schemaVersion": "openxiangda.release-notes/v1",
3
+ "version": "2.17.0",
4
+ "status": "reviewed",
5
+ "title": "OpenXiangda 2.17.0:一行声明生成用户端标准面",
6
+ "summary": "CRUD 视图声明 user: true 即可为普通用户生成“我的记录 + 提交”标准页面(桌面与移动双端),自动接线能力与登录落地,简单 CRUD 应用开箱即有完整用户端闭环。",
7
+ "newFeatures": [
8
+ "resource-records / resource-submit 两种用户标准路由:`/my/<资源代码>` 列表与 `/my/<资源代码>/submit` 提交(移动端 `/m/` 前缀),能力分别绑定资源 read / create。",
9
+ "“我的记录”标准组件:默认仅展示当前登录用户创建的记录(created_by 过滤)、分页、行详情抽屉与提交入口;提交页复用标准生成表单。",
10
+ "首个启用资源自动成为登录落地页与根路径;多资源用 user: { home: true } 显式指定;声明式 /home 占位仍可覆盖。"
11
+ ],
12
+ "fixes": [
13
+ "GenericResourceQuery 新增显式 createdBy 过滤映射 created_by 系统字段,不放宽其他系统字段查询。"
14
+ ],
15
+ "affectedUsers": [
16
+ "为普通登录用户提供提交与查询的简单 CRUD 应用开发者。",
17
+ "使用 openxiangda 2.16 及以下的应用升级后行为零变化(未声明 user 面不受影响)。"
18
+ ],
19
+ "upgradeSteps": [
20
+ "应用精确依赖升级到 openxiangda 2.17.0 并刷新项目 Skill。",
21
+ "在需要用户端的 CRUD 视图上追加 user: true;如需自定义用户端体验,继续用自定义页面覆盖即可。",
22
+ "行级强隔离仍按数据与权限文档声明 dataPolicies;“仅本人”列表是展示过滤。"
23
+ ],
24
+ "knownLimitations": [
25
+ "“我的记录”的本人过滤是查询层展示过滤,非服务端授权边界。",
26
+ "详情抽屉按声明字段只读渲染,暂不支持在用户端编辑已有记录(可继续用自定义页面)。"
27
+ ],
28
+ "compatibility": {
29
+ "node": ">=24",
30
+ "workspaceGenerations": [
31
+ "v2"
32
+ ],
33
+ "platform": "无需平台配合变更;路由与能力契约向后兼容。V1 引擎保持独立。",
34
+ "v1": "不改变 V1 运行时或应用。"
35
+ },
36
+ "issues": [],
37
+ "sha256": "5656c1f523a25d37770bed992d352dc372f3e08f5ce8ca25394443d448d25870",
38
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.17.0"
39
+ }
@@ -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": "37ddcd0b0d5bb157e76bac263609f910ed97e589ad6cbfebe15ca37c1874ecb0"
7
+ "sha256": "5354dcdcc499b57834cffdd9d4fa53cbf684fdbb5bb4b23e0a3eb8b1ed47571b"
8
8
  }
9
9
  ]
10
10
  }
@@ -40,10 +40,10 @@ AI 接到新应用、页面或改版任务时,在同一个 OpenXiangda 工作
40
40
  未创建工作区时使用本 Skill 随根包发布的精确版本:
41
41
 
42
42
  ```bash
43
- pnpm dlx openxiangda@2.15.0 auth status --cwd <应用目录> --base-url <平台地址> --json
44
- pnpm dlx openxiangda@2.15.0 login --cwd <应用目录> --base-url <平台地址>
45
- pnpm dlx openxiangda@2.15.0 create <应用目录> --base-url <同一平台地址>
46
- pnpm dlx openxiangda@2.15.0 skill install --force
43
+ pnpm dlx openxiangda@2.17.0 auth status --cwd <应用目录> --base-url <平台地址> --json
44
+ pnpm dlx openxiangda@2.17.0 login --cwd <应用目录> --base-url <平台地址>
45
+ pnpm dlx openxiangda@2.17.0 create <应用目录> --base-url <同一平台地址>
46
+ pnpm dlx openxiangda@2.17.0 skill install --force
47
47
  ```
48
48
 
49
49
  创建前把产品要求的目标平台明确带入命令,不从旧登录态推断站点。已有工作区从原绑定恢复,平台不一致时先解决登录与目标,不改 link 文件跨站创建。
@@ -63,6 +63,7 @@ pnpm dlx openxiangda@2.15.0 skill install --force
63
63
  | 模糊想法、模块发现、PRD、权限与架构设计 | [产品设计](references/product-design.md)、[交互模式](references/interaction-patterns.md) |
64
64
  | 界面设计、改版、原型和视觉修正 | 先读[设计工作流](references/design-workflow.md),使用 `openxiangda design open` 和 `design cli` 调用原版;[离线方法](references/opendesign-methods.md)与[设计 Craft](references/design-craft.md)仅作补充 |
65
65
  | 理解需求与选择能力 | [开发流程](references/development.md)、[架构](references/concepts.md) |
66
+ | 写 openxiangda.config.ts 声明、避免首轮校验返工 | [声明速查](references/declarations-cheatsheet.md);先扫规则表再动手 |
66
67
  | 模型、CRUD、字段与移动表单 | [业务模块](references/application-foundation.md)、[字段](references/field-components.md) |
67
68
  | 图片压缩、缩略图、附件和缓存 | [图片与附件读取](references/field-components.md#图片缩略图和附件读取):卡片优先缩略图,原图按需,使用平台权限与缓存规则 |
68
69
  | 页面、标准组件与扩展 | [前端](references/frontend.md) |
@@ -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 后不做业务校验;应用负责该动作的输入约束和业务不变量。