@qingflow-tech/qingflow-cli 1.1.43-beta.4 → 1.1.43-beta.5
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.
- package/README.md +3 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-form/SKILL.md +7 -6
- package/skills/qingflow-cli/reference/builder/30-schema-fields.md +3 -2
- package/src/qingflow_mcp/app_form_validator.py +97 -0
- package/src/qingflow_mcp/builder_facade/app_form.py +15 -2
- package/src/qingflow_mcp/builder_facade/app_form_schema.py +50 -5
- package/src/qingflow_mcp/builder_facade/schemas/app_form/manifest.json +5 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/complete.json +7 -12
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/address.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/amount.json +6 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/attachment.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/code_block.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/data_relation.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/datetime.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/department.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/email.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/link.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/long_text.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/member.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/multi_select.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/number.json +6 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/phone.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/q_linker.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/relation.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_choice.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_select.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/subtable.json +7 -12
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/text.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/formula.json +1594 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/summary.json +1 -1
- package/src/qingflow_mcp/cli/commands/builder.py +2 -0
- package/src/qingflow_mcp/server_app_builder.py +7 -2
- package/src/qingflow_mcp/tools/ai_builder_tools.py +13 -3
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Qingflow CLI 是轻流面向开发者和 AI Agent 的命令行工具。它把轻
|
|
|
7
7
|
## 安装
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @qingflow-tech/qingflow-cli@1.1.43-beta.
|
|
10
|
+
npm install @qingflow-tech/qingflow-cli@1.1.43-beta.5
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
推荐从官网复制这一条一键安装 Qingflow CLI:
|
|
@@ -21,7 +21,7 @@ npx qingflow-cli@latest install
|
|
|
21
21
|
## 运行
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npx -y -p @qingflow-tech/qingflow-cli@1.1.43-beta.
|
|
24
|
+
npx -y -p @qingflow-tech/qingflow-cli@1.1.43-beta.5 qingflow
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
全局安装后可以直接运行:
|
|
@@ -62,7 +62,7 @@ qingflow-skills install --agent codex --scope user
|
|
|
62
62
|
如果你通过一次性 `npx -p` 执行 skills 安装,建议加 `--copy`,避免 symlink 指向 npm 临时执行缓存:
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
|
-
npx -y -p @qingflow-tech/qingflow-cli@1.1.43-beta.
|
|
65
|
+
npx -y -p @qingflow-tech/qingflow-cli@1.1.43-beta.5 qingflow-skills install --agent codex --scope user --copy
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
`qingflow-skills install` 默认使用 symlink,且遇到同名 skill 不会覆盖;只有显式传入 `--force` 才会替换。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qingflow-tech/qingflow-cli",
|
|
3
|
-
"version": "1.1.43-beta.
|
|
3
|
+
"version": "1.1.43-beta.5",
|
|
4
4
|
"description": "Human-friendly Qingflow command line interface for auth, record operations, import, tasks, and stable builder flows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/pyproject.toml
CHANGED
|
@@ -21,11 +21,12 @@ Before creating an app, let AppForm derive a stable default icon and color from
|
|
|
21
21
|
|
|
22
22
|
1. Read the AppForm Schema and pin its `schemaVersion` and `apiVersion` for the entire operation.
|
|
23
23
|
2. Read the pinned field Schema for every canonical field type used, including subtable child types.
|
|
24
|
-
3.
|
|
25
|
-
4.
|
|
26
|
-
5.
|
|
27
|
-
6.
|
|
28
|
-
7.
|
|
24
|
+
3. When a field uses `defaultType: 3` or another config accepts a formula, read the pinned formula Schema with CLI `--schema-kind formula` or MCP `schema_kind="formula"`; use its field-reference, operator, and function catalog instead of inventing formula syntax.
|
|
25
|
+
4. For an update, read the draft form with `being_draft=true`. Preserve every field, row, section, and subfield that should remain. For a create, build a declaration without `appKey` and with `packageId`, `spec.appName`, and complete `spec.body`; AppForm derives the icon and color from `spec.appName`.
|
|
26
|
+
5. Keep existing `queId`, `sectionId`, `attachId`, and `aliasId` values from get. Omit them only for new resources. Treat `body`, `subfields`, explicit `attachments`, and custom serial-number `components` as complete target state.
|
|
27
|
+
6. Save the declaration as JSON. In CLI, run `qingflow --json builder app-form validate --schema-version PINNED --file DECLARATION.json` before every apply and fix every reported pointer. In MCP, call apply directly; it performs the same declaration validation internally. YAML is not accepted.
|
|
28
|
+
7. Apply the declaration on the selected surface. Record a newly returned `appKey` immediately. On success, show only `operation`, the non-empty `changes/actions` summary, and warnings. On any non-success result, show the recovery diagnostics, especially deleted/changed field names in `diff/remainingDiff`, and the failed phase.
|
|
29
|
+
8. A successful apply has already read back the draft, verified the complete target, and verified the published form. Do not call get only to verify it again. For any non-success result, follow Recovery; never replay a saved backend payload or edit version.
|
|
29
30
|
|
|
30
31
|
## Read Source
|
|
31
32
|
|
|
@@ -56,7 +57,7 @@ Before creating an app, let AppForm derive a stable default icon and color from
|
|
|
56
57
|
|
|
57
58
|
- Pin one Schema version; never combine details from different versions or silently upgrade recovery work.
|
|
58
59
|
- Ordinary and section rows contain one to four fields. A section is its own outer block. Subtable columns use only the allowed subfield types returned by the subtable detail Schema.
|
|
59
|
-
- Field declarations may set `defaultType` (`1` static, `2` relation/binding, `3` formula) and `defaultValue`; `defaultValue` is `string | null` because the backend `queDefaultValue` is a string, so numeric literals must be passed as strings. Use `null` to clear a default. These properties also apply to subtable fields. Choice defaults belong in `config.defaultOptions`; address, attachment, subtable, and relation defaults belong in `config.defaultValues`. Omit a default when none is intended.
|
|
60
|
+
- Field declarations may set `defaultType` (`1` static, `2` relation/binding, `3` formula) and `defaultValue`; `defaultValue` is `string | null` because the backend `queDefaultValue` is a string, so numeric literals must be passed as strings. For `defaultType: 3`, query the same version with `--schema-kind formula` or `schema_kind="formula"` before composing the formula. Use `null` to clear a default. These properties also apply to subtable fields. Choice defaults belong in `config.defaultOptions`; address, attachment, subtable, and relation defaults belong in `config.defaultValues`. Omit a default when none is intended.
|
|
60
61
|
- For `code_block`, when `config.codeBlockBinding.outputs` is non-empty, the `code` must explicitly assign `qf_output`, for example `qf_output = { score: input.score };`; `return {...}` alone does not satisfy output writeback, and each output `path` must point under `qf_output`.
|
|
61
62
|
- A relation formula default is `config.defaultValueFormula` and must satisfy the pinned `relation` field Schema's `FormulaExpression`; do not infer its syntax from another version or from backend DTO names.
|
|
62
63
|
- If widths are present, every field in that row must have one and the total must equal 100.
|
|
@@ -164,7 +164,7 @@ current CLI payload.
|
|
|
164
164
|
|
|
165
165
|
## Field type reference
|
|
166
166
|
|
|
167
|
-
> **口径**:`field.questionType` 枚举和字段形状以固定版本的 **`qingflow --json builder app-form schema`** 返回结果为准;每个 canonical 类型还可用 `--field-type TYPE`
|
|
167
|
+
> **口径**:`field.questionType` 枚举和字段形状以固定版本的 **`qingflow --json builder app-form schema`** 返回结果为准;每个 canonical 类型还可用 `--field-type TYPE` 获取细节,`defaultType: 3` 的公式语法和函数目录用 `--schema-kind formula` 获取。不要从已退休的 `app_schema_apply` contract 推断当前 AppForm payload。
|
|
168
168
|
> **CLI**:表单写入使用 `qingflow [--profile NAME] --json builder app-form schema/get/validate/apply`;`validate/apply` 接受 `--file`,`get` 接受 `--app-key`。旧 `builder schema apply` 及 `builder layout apply` 不在当前 parser 的公开命令集合中。
|
|
169
169
|
> **新建应用主规则**:`spec.body` 非空时必须用 `spec.dataTitleField` 唯一选择一个顶层字段作为数据标题;数据封面可选,只能用 `spec.dataCoverField` 选择顶层 `attachment` 字段。
|
|
170
170
|
> **权限**:包内新建应用按后端 `CreateAppBean` 链路只预检目标包 **AddAppAuth**;已有应用的字段/基础信息变更才走应用自身 **EditAppAuth**。
|
|
@@ -292,7 +292,8 @@ current CLI payload.
|
|
|
292
292
|
|------|------|
|
|
293
293
|
| **当前完整系统主入口** | [20-build-complete-system.md](./20-build-complete-system.md) |
|
|
294
294
|
| **历史字段类型能力探针(不是新建主模板)** | [schema_apply_add_fields_all_types.json](../examples/schema/schema_apply_add_fields_all_types.json)(仅用于旧字段能力覆盖测试;当前新建和更新统一使用上文 AppForm) |
|
|
295
|
-
| **`q_linker` / `code_block` / relation
|
|
295
|
+
| **`q_linker` / `code_block` / relation 配置**(契约内置形状) | `qingflow --json builder app-form schema --schema-version VERSION --field-type TYPE` 返回的固定版本字段详情 |
|
|
296
|
+
| **默认值与其他公式文本** | `qingflow --json builder app-form schema --schema-version VERSION --schema-kind formula` 返回的同版本公式语法、字段引用、运算符和函数目录 |
|
|
296
297
|
| 最小新建字段(1 个 text 标题字段) | [schema_add_fields_minimal.example.json](../examples/schema/schema_add_fields_minimal.example.json) |
|
|
297
298
|
| 历史 scalar / complex 批测(可选) | [_batch_schema_scalar.json](../examples/schema/_batch_schema_scalar.json)、[_batch_schema_complex.json](../examples/schema/_batch_schema_complex.json) |
|
|
298
299
|
|
|
@@ -23,6 +23,7 @@ _INTEGRATION_OUTPUT_TARGET_FIELD_TYPES = {
|
|
|
23
23
|
"multi_select",
|
|
24
24
|
}
|
|
25
25
|
_Q_LINKER_TEMPLATE_RE = re.compile(r"\{\{(?P<key>[^{}\s]+)\}\}")
|
|
26
|
+
_FORMULA_IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
class AppFormValidationError(ValueError):
|
|
@@ -68,6 +69,18 @@ def _local_schema_call(schema_version: str | None, field_type: str | None) -> di
|
|
|
68
69
|
return {"status": "success", **app_form_schema_registry().schema(schema_version=schema_version, field_type=field_type)}
|
|
69
70
|
|
|
70
71
|
|
|
72
|
+
def _supported_formula_functions(schema_version: str) -> frozenset[str]:
|
|
73
|
+
"""Load the version-pinned formula function catalogue used by local validation."""
|
|
74
|
+
from .builder_facade.app_form_schema import app_form_schema_registry
|
|
75
|
+
|
|
76
|
+
response = app_form_schema_registry().schema(schema_version=schema_version, schema_kind="formula")
|
|
77
|
+
schema = response.get("schema") if isinstance(response, dict) else None
|
|
78
|
+
functions = schema.get("x-qingflow-functions") if isinstance(schema, dict) else None
|
|
79
|
+
if not isinstance(functions, dict) or not functions:
|
|
80
|
+
raise RuntimeError(f"/: AppForm formula Schema has no function catalogue for {schema_version}")
|
|
81
|
+
return frozenset(str(name).upper() for name in functions if isinstance(name, str) and name)
|
|
82
|
+
|
|
83
|
+
|
|
71
84
|
def _compose(summary: dict[str, Any], details: list[dict[str, Any]]) -> dict[str, Any]:
|
|
72
85
|
if not details:
|
|
73
86
|
raise RuntimeError("at least one field Schema is required to compose AppForm validation")
|
|
@@ -101,6 +114,87 @@ def _pointer(parts: Iterable[Any]) -> str:
|
|
|
101
114
|
return "/" + "/".join(encoded) if encoded else "/"
|
|
102
115
|
|
|
103
116
|
|
|
117
|
+
def _formula_function_calls(expression: str) -> set[str]:
|
|
118
|
+
"""Extract calls outside quoted string literals without parsing the full formula grammar."""
|
|
119
|
+
calls: set[str] = set()
|
|
120
|
+
index = 0
|
|
121
|
+
while index < len(expression):
|
|
122
|
+
character = expression[index]
|
|
123
|
+
if character in {"'", '"'}:
|
|
124
|
+
quote = character
|
|
125
|
+
index += 1
|
|
126
|
+
while index < len(expression):
|
|
127
|
+
if expression[index] == "\\":
|
|
128
|
+
index += 2
|
|
129
|
+
continue
|
|
130
|
+
if expression[index] == quote:
|
|
131
|
+
index += 1
|
|
132
|
+
break
|
|
133
|
+
index += 1
|
|
134
|
+
continue
|
|
135
|
+
match = _FORMULA_IDENTIFIER_RE.match(expression, index)
|
|
136
|
+
if match is None:
|
|
137
|
+
index += 1
|
|
138
|
+
continue
|
|
139
|
+
name = match.group(0)
|
|
140
|
+
cursor = match.end()
|
|
141
|
+
while cursor < len(expression) and expression[cursor].isspace():
|
|
142
|
+
cursor += 1
|
|
143
|
+
if cursor < len(expression) and expression[cursor] in {"(", "("}:
|
|
144
|
+
calls.add(name)
|
|
145
|
+
index = match.end()
|
|
146
|
+
return calls
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _iter_formula_expressions(payload: dict[str, Any]) -> Iterable[tuple[str, str]]:
|
|
150
|
+
spec = payload.get("spec")
|
|
151
|
+
body = spec.get("body") if isinstance(spec, dict) else None
|
|
152
|
+
if not isinstance(body, list):
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
def visit(field: Any, path: str) -> Iterable[tuple[str, str]]:
|
|
156
|
+
if not isinstance(field, dict):
|
|
157
|
+
return
|
|
158
|
+
if field.get("defaultType") == 3 and isinstance(field.get("defaultValue"), str):
|
|
159
|
+
yield f"{path}/defaultValue", field["defaultValue"]
|
|
160
|
+
config = field.get("config") if isinstance(field.get("config"), dict) else {}
|
|
161
|
+
default_value_formula = config.get("defaultValueFormula")
|
|
162
|
+
if isinstance(default_value_formula, str):
|
|
163
|
+
yield f"{path}/config/defaultValueFormula", default_value_formula
|
|
164
|
+
filters = config.get("filters") if isinstance(config.get("filters"), dict) else {}
|
|
165
|
+
if filters.get("mode") == "advanced" and isinstance(filters.get("formula"), str):
|
|
166
|
+
yield f"{path}/config/filters/formula", filters["formula"]
|
|
167
|
+
for child_index, child in enumerate(field.get("subfields") or []):
|
|
168
|
+
yield from visit(child, f"{path}/subfields/{child_index}")
|
|
169
|
+
|
|
170
|
+
for block_index, block in enumerate(body):
|
|
171
|
+
if not isinstance(block, dict):
|
|
172
|
+
continue
|
|
173
|
+
rows = [(block, f"/spec/body/{block_index}")] if block.get("kind") == "row" else [
|
|
174
|
+
(row, f"/spec/body/{block_index}/rows/{row_index}")
|
|
175
|
+
for row_index, row in enumerate(block.get("rows") or [])
|
|
176
|
+
]
|
|
177
|
+
for row, row_path in rows:
|
|
178
|
+
if not isinstance(row, dict):
|
|
179
|
+
continue
|
|
180
|
+
for field_index, field in enumerate(row.get("fields") or []):
|
|
181
|
+
yield from visit(field, f"{row_path}/fields/{field_index}")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _formula_errors(payload: dict[str, Any], supported_functions: frozenset[str]) -> list[tuple[str, str]]:
|
|
185
|
+
errors: list[tuple[str, str]] = []
|
|
186
|
+
for pointer, expression in _iter_formula_expressions(payload):
|
|
187
|
+
unknown = sorted(name for name in _formula_function_calls(expression) if name.upper() not in supported_functions)
|
|
188
|
+
for name in unknown:
|
|
189
|
+
errors.append(
|
|
190
|
+
(
|
|
191
|
+
pointer,
|
|
192
|
+
f"unsupported formula function '{name}'; use a function declared by builder app-form schema --schema-kind formula",
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
return errors
|
|
196
|
+
|
|
197
|
+
|
|
104
198
|
def _static_errors(payload: dict[str, Any]) -> list[tuple[str, str]]:
|
|
105
199
|
errors: list[tuple[str, str]] = []
|
|
106
200
|
spec = payload.get("spec")
|
|
@@ -346,6 +440,7 @@ def validate_declaration(
|
|
|
346
440
|
) from error
|
|
347
441
|
if declaration.get("apiVersion") != api_version:
|
|
348
442
|
raise ValueError(f"/apiVersion: expected {api_version!r} for schemaVersion {pinned!r}")
|
|
443
|
+
supported_formula_functions = _supported_formula_functions(pinned)
|
|
349
444
|
types = _field_types(declaration)
|
|
350
445
|
details = [schema_call(pinned, field_type) for field_type in (types or ("text",))]
|
|
351
446
|
for detail in details:
|
|
@@ -359,6 +454,8 @@ def validate_declaration(
|
|
|
359
454
|
found[(_pointer(error.absolute_path), f"{error.validator}: {schema_error_message(error)}")] = None
|
|
360
455
|
for pointer, message in _static_errors(declaration):
|
|
361
456
|
found[(pointer, message)] = None
|
|
457
|
+
for pointer, message in _formula_errors(declaration, supported_formula_functions):
|
|
458
|
+
found[(pointer, message)] = None
|
|
362
459
|
if found:
|
|
363
460
|
issues = [{"pointer": pointer, "message": message} for pointer, message in sorted(found)]
|
|
364
461
|
raise AppFormValidationError(issues, schema_version=pinned, api_version=api_version)
|
|
@@ -2834,9 +2834,22 @@ class AppFormReconciler:
|
|
|
2834
2834
|
self.normalizer = AppFormNormalizer()
|
|
2835
2835
|
self.compiler = AppFormCompiler(apps)
|
|
2836
2836
|
|
|
2837
|
-
def schema(
|
|
2837
|
+
def schema(
|
|
2838
|
+
self,
|
|
2839
|
+
*,
|
|
2840
|
+
schema_version: str | None,
|
|
2841
|
+
field_type: str | None,
|
|
2842
|
+
schema_kind: str | None = None,
|
|
2843
|
+
) -> JSONObject:
|
|
2838
2844
|
try:
|
|
2839
|
-
return {
|
|
2845
|
+
return {
|
|
2846
|
+
"status": "success",
|
|
2847
|
+
**self.registry.schema(
|
|
2848
|
+
schema_version=schema_version,
|
|
2849
|
+
field_type=field_type,
|
|
2850
|
+
schema_kind=schema_kind,
|
|
2851
|
+
),
|
|
2852
|
+
}
|
|
2840
2853
|
except AppFormContractError as error:
|
|
2841
2854
|
return self._failure(error.error_code, error.message, "SCHEMA", details=error.details)
|
|
2842
2855
|
|
|
@@ -139,6 +139,7 @@ class AppFormSchemaContext:
|
|
|
139
139
|
api_version: str
|
|
140
140
|
complete_schema: JSONObject
|
|
141
141
|
supported_types: tuple[str, ...]
|
|
142
|
+
supported_schema_kinds: tuple[str, ...]
|
|
142
143
|
contract_source: str
|
|
143
144
|
contract_version: str
|
|
144
145
|
normalizer_version: str
|
|
@@ -242,6 +243,9 @@ class AppFormSchemaRegistry:
|
|
|
242
243
|
api_version = str(metadata.get("apiVersion") or "")
|
|
243
244
|
complete = self._read_json(root / version / "complete.json")
|
|
244
245
|
supported = tuple(str(item) for item in metadata.get("fieldTypes") or ())
|
|
246
|
+
supported_schema_kinds = tuple(str(item) for item in metadata.get("schemaKinds") or ("summary", "field"))
|
|
247
|
+
if not {"summary", "field"}.issubset(supported_schema_kinds):
|
|
248
|
+
raise RuntimeError(f"AppForm schema version must support summary and field Schemas: {version}")
|
|
245
249
|
if complete.get("properties", {}).get("apiVersion", {}).get("const") != api_version:
|
|
246
250
|
raise RuntimeError(f"AppForm schema apiVersion mismatch for {version}")
|
|
247
251
|
schema_id = str(complete.get("$id") or "")
|
|
@@ -254,6 +258,11 @@ class AppFormSchemaRegistry:
|
|
|
254
258
|
raise RuntimeError(f"AppForm summary apiVersion mismatch for {version}")
|
|
255
259
|
Draft202012Validator.check_schema(summary)
|
|
256
260
|
self._validate_local_refs(summary, root / version / "summary.json")
|
|
261
|
+
if "formula" in supported_schema_kinds:
|
|
262
|
+
formula_path = root / version / "formula.json"
|
|
263
|
+
formula = self._read_json(formula_path)
|
|
264
|
+
Draft202012Validator.check_schema(formula)
|
|
265
|
+
self._validate_local_refs(formula, formula_path)
|
|
257
266
|
for field_type in supported:
|
|
258
267
|
field_path = root / version / "fields" / f"{field_type}.json"
|
|
259
268
|
field_schema = self._read_json(field_path)
|
|
@@ -264,6 +273,7 @@ class AppFormSchemaRegistry:
|
|
|
264
273
|
api_version,
|
|
265
274
|
complete,
|
|
266
275
|
supported,
|
|
276
|
+
supported_schema_kinds,
|
|
267
277
|
str(metadata.get("contractSource") or "app_form"),
|
|
268
278
|
str(metadata.get("contractVersion") or version),
|
|
269
279
|
str(metadata.get("normalizerVersion") or version),
|
|
@@ -272,12 +282,46 @@ class AppFormSchemaRegistry:
|
|
|
272
282
|
self._contexts[version] = context
|
|
273
283
|
self._api_to_version[api_version] = version
|
|
274
284
|
|
|
275
|
-
def schema(
|
|
285
|
+
def schema(
|
|
286
|
+
self,
|
|
287
|
+
*,
|
|
288
|
+
schema_version: str | None = None,
|
|
289
|
+
field_type: str | None = None,
|
|
290
|
+
schema_kind: str | None = None,
|
|
291
|
+
) -> JSONObject:
|
|
276
292
|
context = self.resolve_schema_version(schema_version)
|
|
277
|
-
if field_type is None
|
|
293
|
+
requested_kind = str(schema_kind or ("field" if field_type is not None else "summary")).strip()
|
|
294
|
+
if requested_kind not in context.supported_schema_kinds:
|
|
295
|
+
raise AppFormContractError(
|
|
296
|
+
"UNSUPPORTED_SCHEMA_KIND",
|
|
297
|
+
f"unsupported AppForm schema kind: {requested_kind}",
|
|
298
|
+
details={"supportedKinds": list(context.supported_schema_kinds)},
|
|
299
|
+
)
|
|
300
|
+
if requested_kind == "formula":
|
|
301
|
+
if field_type is not None:
|
|
302
|
+
raise AppFormContractError(
|
|
303
|
+
"INVALID_SCHEMA_SELECTION",
|
|
304
|
+
"field_type cannot be combined with schema_kind=formula",
|
|
305
|
+
details={"schemaKind": requested_kind, "fieldType": field_type},
|
|
306
|
+
)
|
|
307
|
+
schema = self._read_json(self.root / context.schema_version / "formula.json")
|
|
308
|
+
resolved_kind = "formula"
|
|
309
|
+
elif requested_kind == "summary":
|
|
310
|
+
if field_type is not None:
|
|
311
|
+
raise AppFormContractError(
|
|
312
|
+
"INVALID_SCHEMA_SELECTION",
|
|
313
|
+
"field_type requires schema_kind=field or an omitted schema_kind",
|
|
314
|
+
details={"schemaKind": requested_kind, "fieldType": field_type},
|
|
315
|
+
)
|
|
278
316
|
schema = self._read_json(self.root / context.schema_version / "summary.json")
|
|
279
|
-
|
|
317
|
+
resolved_kind = "summary"
|
|
280
318
|
else:
|
|
319
|
+
if field_type is None:
|
|
320
|
+
raise AppFormContractError(
|
|
321
|
+
"INVALID_SCHEMA_SELECTION",
|
|
322
|
+
"schema_kind=field requires field_type",
|
|
323
|
+
details={"schemaKind": requested_kind, "supportedTypes": list(context.supported_types)},
|
|
324
|
+
)
|
|
281
325
|
if field_type not in context.supported_types:
|
|
282
326
|
raise AppFormContractError(
|
|
283
327
|
"UNSUPPORTED_SCHEMA_FIELD_TYPE",
|
|
@@ -285,14 +329,15 @@ class AppFormSchemaRegistry:
|
|
|
285
329
|
details={"supportedTypes": list(context.supported_types)},
|
|
286
330
|
)
|
|
287
331
|
schema = self._read_json(self.root / context.schema_version / "fields" / f"{field_type}.json")
|
|
288
|
-
|
|
332
|
+
resolved_kind = "field"
|
|
289
333
|
return {
|
|
290
334
|
"schemaVersion": context.schema_version,
|
|
291
335
|
"apiVersion": context.api_version,
|
|
292
336
|
"latestVersion": self.latest_version,
|
|
293
337
|
"storedVersions": list(self.stored_versions),
|
|
294
338
|
"enabledVersions": list(self.enabled_versions),
|
|
295
|
-
"
|
|
339
|
+
"supportedSchemaKinds": list(context.supported_schema_kinds),
|
|
340
|
+
"schemaKind": resolved_kind,
|
|
296
341
|
"fieldType": field_type,
|
|
297
342
|
"schema": schema,
|
|
298
343
|
}
|
|
@@ -1593,10 +1593,12 @@
|
|
|
1593
1593
|
]
|
|
1594
1594
|
},
|
|
1595
1595
|
"currencyType": {
|
|
1596
|
-
"
|
|
1597
|
-
"description": "货币类型 ID;后端发布要求为非空正整数,即使 currencyStatus 为 false 也不得传 null。无需指定时省略并使用默认值 1。",
|
|
1596
|
+
"description": "货币类型 ID。",
|
|
1598
1597
|
"minimum": 1,
|
|
1599
|
-
"type":
|
|
1598
|
+
"type": [
|
|
1599
|
+
"integer",
|
|
1600
|
+
"null"
|
|
1601
|
+
]
|
|
1600
1602
|
},
|
|
1601
1603
|
"decimalDigit": {
|
|
1602
1604
|
"description": "小数位数。",
|
|
@@ -2083,7 +2085,7 @@
|
|
|
2083
2085
|
"type": "object"
|
|
2084
2086
|
},
|
|
2085
2087
|
"ScalarDefaultValue": {
|
|
2086
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
2088
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
2087
2089
|
"type": [
|
|
2088
2090
|
"null",
|
|
2089
2091
|
"string"
|
|
@@ -2624,14 +2626,7 @@
|
|
|
2624
2626
|
]
|
|
2625
2627
|
},
|
|
2626
2628
|
"tableRowHeight": {
|
|
2627
|
-
"
|
|
2628
|
-
"enum": [
|
|
2629
|
-
"compact",
|
|
2630
|
-
"moderate",
|
|
2631
|
-
"easy",
|
|
2632
|
-
"highest",
|
|
2633
|
-
null
|
|
2634
|
-
],
|
|
2629
|
+
"minLength": 1,
|
|
2635
2630
|
"type": [
|
|
2636
2631
|
"string",
|
|
2637
2632
|
"null"
|
|
@@ -359,7 +359,7 @@
|
|
|
359
359
|
"type": "object"
|
|
360
360
|
},
|
|
361
361
|
"ScalarDefaultValue": {
|
|
362
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
362
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
363
363
|
"type": [
|
|
364
364
|
"null",
|
|
365
365
|
"string"
|
|
@@ -137,10 +137,12 @@
|
|
|
137
137
|
]
|
|
138
138
|
},
|
|
139
139
|
"currencyType": {
|
|
140
|
-
"
|
|
141
|
-
"description": "货币类型 ID;后端发布要求为非空正整数,即使 currencyStatus 为 false 也不得传 null。无需指定时省略并使用默认值 1。",
|
|
140
|
+
"description": "货币类型 ID。",
|
|
142
141
|
"minimum": 1,
|
|
143
|
-
"type":
|
|
142
|
+
"type": [
|
|
143
|
+
"integer",
|
|
144
|
+
"null"
|
|
145
|
+
]
|
|
144
146
|
},
|
|
145
147
|
"decimalDigit": {
|
|
146
148
|
"description": "小数位数。",
|
|
@@ -201,7 +203,7 @@
|
|
|
201
203
|
"type": "object"
|
|
202
204
|
},
|
|
203
205
|
"ScalarDefaultValue": {
|
|
204
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
206
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
205
207
|
"type": [
|
|
206
208
|
"null",
|
|
207
209
|
"string"
|
|
@@ -392,7 +392,7 @@
|
|
|
392
392
|
"type": "object"
|
|
393
393
|
},
|
|
394
394
|
"ScalarDefaultValue": {
|
|
395
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
395
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
396
396
|
"type": [
|
|
397
397
|
"null",
|
|
398
398
|
"string"
|
|
@@ -284,7 +284,7 @@
|
|
|
284
284
|
"type": "object"
|
|
285
285
|
},
|
|
286
286
|
"ScalarDefaultValue": {
|
|
287
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
287
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
288
288
|
"type": [
|
|
289
289
|
"null",
|
|
290
290
|
"string"
|
|
@@ -546,7 +546,7 @@
|
|
|
546
546
|
"type": "object"
|
|
547
547
|
},
|
|
548
548
|
"ScalarDefaultValue": {
|
|
549
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
549
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
550
550
|
"type": [
|
|
551
551
|
"null",
|
|
552
552
|
"string"
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
"type": "object"
|
|
118
118
|
},
|
|
119
119
|
"ScalarDefaultValue": {
|
|
120
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
120
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
121
121
|
"type": [
|
|
122
122
|
"null",
|
|
123
123
|
"string"
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
"type": "object"
|
|
118
118
|
},
|
|
119
119
|
"ScalarDefaultValue": {
|
|
120
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
120
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
121
121
|
"type": [
|
|
122
122
|
"null",
|
|
123
123
|
"string"
|
|
@@ -264,7 +264,7 @@
|
|
|
264
264
|
"type": "object"
|
|
265
265
|
},
|
|
266
266
|
"ScalarDefaultValue": {
|
|
267
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
267
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
268
268
|
"type": [
|
|
269
269
|
"null",
|
|
270
270
|
"string"
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"type": "object"
|
|
113
113
|
},
|
|
114
114
|
"ScalarDefaultValue": {
|
|
115
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
115
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
116
116
|
"type": [
|
|
117
117
|
"null",
|
|
118
118
|
"string"
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"type": "object"
|
|
113
113
|
},
|
|
114
114
|
"ScalarDefaultValue": {
|
|
115
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
115
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
116
116
|
"type": [
|
|
117
117
|
"null",
|
|
118
118
|
"string"
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"type": "object"
|
|
113
113
|
},
|
|
114
114
|
"ScalarDefaultValue": {
|
|
115
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
115
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
116
116
|
"type": [
|
|
117
117
|
"null",
|
|
118
118
|
"string"
|
|
@@ -404,7 +404,7 @@
|
|
|
404
404
|
"type": "object"
|
|
405
405
|
},
|
|
406
406
|
"ScalarDefaultValue": {
|
|
407
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
407
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
408
408
|
"type": [
|
|
409
409
|
"null",
|
|
410
410
|
"string"
|
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
"type": "object"
|
|
181
181
|
},
|
|
182
182
|
"ScalarDefaultValue": {
|
|
183
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
183
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
184
184
|
"type": [
|
|
185
185
|
"null",
|
|
186
186
|
"string"
|
|
@@ -137,10 +137,12 @@
|
|
|
137
137
|
]
|
|
138
138
|
},
|
|
139
139
|
"currencyType": {
|
|
140
|
-
"
|
|
141
|
-
"description": "货币类型 ID;后端发布要求为非空正整数,即使 currencyStatus 为 false 也不得传 null。无需指定时省略并使用默认值 1。",
|
|
140
|
+
"description": "货币类型 ID。",
|
|
142
141
|
"minimum": 1,
|
|
143
|
-
"type":
|
|
142
|
+
"type": [
|
|
143
|
+
"integer",
|
|
144
|
+
"null"
|
|
145
|
+
]
|
|
144
146
|
},
|
|
145
147
|
"decimalDigit": {
|
|
146
148
|
"description": "小数位数。",
|
|
@@ -201,7 +203,7 @@
|
|
|
201
203
|
"type": "object"
|
|
202
204
|
},
|
|
203
205
|
"ScalarDefaultValue": {
|
|
204
|
-
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null
|
|
206
|
+
"description": "后端 queDefaultValue 支持的普通字段默认值;非空值必须是字符串,显式 null 表示清除默认值。数字和布尔值应先转换为字符串,对象和数组必须使用字段专用默认值配置。defaultType 为 3 时,通过 AppForm Schema 查询能力传入当前 schema_version 并设置 schema_kind=formula 获取公式语法、字段引用、运算符和全部函数说明;CLI 使用 builder app-form schema --schema-kind formula。需要配置结构化默认值时,传入当前 schema_version,并将字段的 questionType 作为 field_type 获取字段详细 Schema。",
|
|
205
207
|
"type": [
|
|
206
208
|
"null",
|
|
207
209
|
"string"
|