diff --git a/DESIGN.md b/DESIGN.md index 56ce418..ae54ae5 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,34 +1,143 @@ -# trans-form-middleware — Design (Q1–Q61) +# @windychen/trans-form-middleware — Design (Q1–Q61) -Status: **implementation scaffold** (v0.1.0) +> 设计基线:grill-me 61 题访谈结论(2026-08-06)。配套包:`@windychen/trans-form-component`(Vue 3 动态查询表单)。 -Companion package: `@windychen/trans-form-component` (Vue 3 dynamic query form). +## 角色定位 -## Role +接收组件输出的 filter 数组,解析为结构化条件 `{ field, query, op, value, logic?, values?, empty? }[]`。**不生成 SQL** —— 由上层 ORM/DAO 适配器消费输出。 -Receive component filter output → structured `{ field, query, op, value, logic?, values?, empty? }[]`. **No SQL generation.** +```js +import { parseFilters } from '@windychen/trans-form-middleware'; -## Key decisions +const filters = parseFilters( + [ + { field: 'name', value: 'xx', query: 'EQ' }, + { field: 'age', value: '18,30', query: 'BETWEEN', logic: 'AND' } + ], + { allowedFields: ['name', 'age'] } +); +// → [{ field: 'name', query: 'EQ', op: '=', value: 'xx' }, +// { field: 'age', query: 'BETWEEN', op: 'BETWEEN', value: '18,30', values: ['18', '30'], logic: 'AND' }] +``` -| # | Decision | -|---|----------| -| 1–2 | Node.js pure JS; structured filters only | -| 8 | Dual fields: `query` (code) + `op` (symbol) | -| 14 | `mode`: pass-through (default) / flat / tree | -| 24–25 | Zero deps; ESM only; no bundling (`src/index.js`) | -| 38 | `empty: true` preserved by default | -| 39 | Built-in `DEFAULT_OPERATOR_MAP`, merge via `operatorMap` | -| 41 | Multi-function exports: `parseFilters`, `toTree`, `mapOperators`, … | -| 44 | `strict` default false | -| 46 | Multi-value: `value` (string) + `values` (parsed array) | -| 49–50 | `allowedFields` required; `unknownField: 'error'` default | -| 60 | Component emits all state flags; middleware skips `disabled` by default | -| 61 | Also skips `hidden`; `skipEmpty` option controls empty items | +--- -## Output example +## 一、架构与发布 + +| # | 决策 | +|---|------| +| 1 | middleware:**Node.js 纯 JS** | +| 2 | middleware 输出:**结构化过滤器**,不碰 SQL | +| 3 | **两个独立 Gitea 仓库**,各自 CI、各自发版 | +| 24 | middleware:**零依赖**;后续另做 Knex/TypeORM 适配包 | +| 25 | middleware 发布:**ESM only** | +| 27 | middleware 构建:**不打包**,`package.json` 指向 `src/index.js` | +| 28 | 测试:**Vitest**,CI 里 `npm test` 通过才发布 | +| 29 | 版本号:**完全独立**,兼容性写 README | +| 21 | CI:**复制 windychen-utils 模式**(三关键词 → Verdaccio + Gitea Packages + Release + 钉钉) | + +--- + +## 二、解析规则 + +### 输出形态 + +| # | 决策 | +|---|------| +| 8 | 输出同时保留 **`query`(原始码)和 `op`(标准符号)** | +| 14 | `parseOptions.mode`:flat / tree / pass-through,**默认透传 logic** | +| 46 | 多值:同时保留 **`value`(原串)和 `values`(解析数组)** | + +### 过滤与校验 + +| # | 决策 | +|---|------| +| 44 | `parseOptions.strict`,**默认 false(宽松)** | +| 49 | 必须传 **`parseOptions.allowedFields` 白名单** | +| 50 | `unknownField: 'error' \| 'skip'`,**默认 `'error'`** | + +### 状态标记处理(Q60–Q61) + +| # | 决策 | +|---|------| +| 60 | 组件 filters **全部带状态标记**输出;middleware **默认忽略 disabled**,readOnly 正常解析 | +| 61 | `hidden: true` **默认跳过**(与 disabled 一致);`empty: true` **默认保留**(原样透传),可通过 `parseOptions.skipEmpty: true/false` 控制是否跳过 | + +### 空值 + +| # | 决策 | +|---|------| +| 38 | `empty: true`:**原样保留**,调用方自行过滤(`skipEmpty` 可覆盖) | +| 9 | 组件侧空 value 带 `empty: true` 标记输出 | + +--- + +## 三、操作符 + +### 内置码表(Q39) + +| # | 决策 | +|---|------| +| 39 | 内置默认 **`DEFAULT_OPERATOR_MAP`**,可 merge/覆盖(`operatorMap` 选项) | +| 12 | v1 操作符:比较 + LIKE/IN/NOT_IN + BETWEEN/IS_NULL/IS_NOT_NULL | + +| 码 | 符号 | 说明 | +|----|------|------| +| `EQ` | `=` | 等于 | +| `NE` | `<>` | 不等于 | +| `GT` | `>` | 大于 | +| `GTE` | `>=` | 大于等于 | +| `LT` | `<` | 小于 | +| `LTE` | `<=` | 小于等于 | +| `LIKE` | `LIKE` | 模糊 | +| `IN` | `IN` | 属于 | +| `NOT_IN` | `NOT IN` | 不属于 | +| `BETWEEN` | `BETWEEN` | 区间 | +| `IS_NULL` | `IS NULL` | 为空 | +| `IS_NOT_NULL` | `IS NOT NULL` | 非空 | + +--- + +## 四、API 表面 + +| # | 决策 | +|---|------| +| 40 | 支持 **`setFilters()` / `loadFilters()`** 回填 | +| 41 | 多函数导出:`parseFilters` / `toTree` / `mapOperators` / `splitMultiValue` 等 | + +### 导出函数 + +| 函数 | 作用 | +|------|------| +| `parseFilters(filters, parseOptions?)` | 主入口:校验白名单、映射操作符、处理状态标记、多值拆分 | +| `toTree(filters, parseOptions?)` | 按 `logic` 组装嵌套分组树 | +| `mapOperators(filters, operatorMap?)` | 只做操作符码 → 符号映射 | +| `splitMultiValue(value, separator?)` | 逗号分隔字符串 → 数组 | + +### parseOptions 汇总 + +| 选项 | 默认 | 说明 | +|------|------|------| +| `allowedFields` | **必传** | 字段白名单 | +| `unknownField` | `'error'` | 未知字段:报错或跳过 | +| `strict` | `false` | 宽松/严格模式 | +| `mode` | `pass-through` | flat / tree / pass-through | +| `operatorMap` | `DEFAULT_OPERATOR_MAP` | 合并/覆盖码表 | +| `skipEmpty` | `false` | 是否跳过 `empty: true` 项 | +| `skipFlags` | `['disabled', 'hidden']` | 默认跳过的状态标记 | + +--- + +## 五、输出示例 ```json [ + { + "field": "name", + "query": "EQ", + "op": "=", + "value": "xx" + }, { "field": "tags", "query": "IN", @@ -36,14 +145,35 @@ Receive component filter output → structured `{ field, query, op, value, logic "value": "a,b,c", "values": ["a", "b", "c"], "logic": "OR" + }, + { + "field": "status", + "query": "IS_NULL", + "op": "IS NULL", + "value": "", + "empty": true, + "logic": "AND" } ] ``` -## Operator map +--- -`EQ→=`, `NE→<>`, `GT→>`, `GTE→>=`, `LT→<`, `LTE→<=`, `LIKE→LIKE`, `IN→IN`, `NOT_IN→NOT IN`, `BETWEEN→BETWEEN`, `IS_NULL→IS NULL`, `IS_NOT_NULL→IS NOT NULL` +## 六、与 component 的协议对齐 -## CI +| 项 | 约定 | +|----|------| +| 输入 | component `getFilters()` / `@change` 输出 | +| 输出字段 | `field`、`value`、`query`、`op`、`logic?`、`values?`、`empty?` | +| 状态标记 | `disabled` / `hidden` 默认跳过;`readOnly` 正常解析 | +| 空值 | `empty: true` 默认保留,`skipEmpty` 可配 | -Copy of windychen-utils pattern: commit message must contain `chore` + `版本` + `更新` to trigger publish. +--- + +## 七、CI 触发约定 + +commit message 必须同时包含 `chore` + `版本` + `更新` 才触发构建发布;否则 job 显示 skipped(设计行为,防误触)。 + +```bash +git commit -m "chore: 版本 0.1.0 更新 ..." +```