@lark-base-open/base-database-cli 1.1.1 → 1.3.8

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 (199) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/LICENSE +6 -0
  3. package/README.md +47 -27
  4. package/dist/ai-functions.d.ts +53 -0
  5. package/dist/ai-functions.d.ts.map +1 -0
  6. package/dist/ai-functions.js +436 -0
  7. package/dist/ai-functions.js.map +1 -0
  8. package/dist/archive.d.ts +21 -0
  9. package/dist/archive.d.ts.map +1 -1
  10. package/dist/archive.js +69 -10
  11. package/dist/archive.js.map +1 -1
  12. package/dist/auth.d.ts +5 -0
  13. package/dist/auth.d.ts.map +1 -1
  14. package/dist/auth.js +26 -3
  15. package/dist/auth.js.map +1 -1
  16. package/dist/check.d.ts +74 -0
  17. package/dist/check.d.ts.map +1 -0
  18. package/dist/check.js +283 -0
  19. package/dist/check.js.map +1 -0
  20. package/dist/dependency-policy.d.ts +10 -0
  21. package/dist/dependency-policy.d.ts.map +1 -0
  22. package/dist/dependency-policy.js +162 -0
  23. package/dist/dependency-policy.js.map +1 -0
  24. package/dist/deploy.d.ts +5 -0
  25. package/dist/deploy.d.ts.map +1 -1
  26. package/dist/deploy.js +141 -176
  27. package/dist/deploy.js.map +1 -1
  28. package/dist/environment-variables.d.ts +52 -0
  29. package/dist/environment-variables.d.ts.map +1 -0
  30. package/dist/environment-variables.js +563 -0
  31. package/dist/environment-variables.js.map +1 -0
  32. package/dist/environment.d.ts +1 -1
  33. package/dist/environment.js +1 -1
  34. package/dist/environment.js.map +1 -1
  35. package/dist/function-input-types.d.ts +10 -0
  36. package/dist/function-input-types.d.ts.map +1 -0
  37. package/dist/function-input-types.js +160 -0
  38. package/dist/function-input-types.js.map +1 -0
  39. package/dist/function-schema.d.ts.map +1 -1
  40. package/dist/function-schema.js +89 -36
  41. package/dist/function-schema.js.map +1 -1
  42. package/dist/init-field-retry.d.ts +17 -0
  43. package/dist/init-field-retry.d.ts.map +1 -0
  44. package/dist/init-field-retry.js +127 -0
  45. package/dist/init-field-retry.js.map +1 -0
  46. package/dist/init-journal.d.ts +98 -0
  47. package/dist/init-journal.d.ts.map +1 -0
  48. package/dist/init-journal.js +492 -0
  49. package/dist/init-journal.js.map +1 -0
  50. package/dist/init-schema.d.ts +8 -0
  51. package/dist/init-schema.d.ts.map +1 -0
  52. package/dist/init-schema.js +423 -0
  53. package/dist/init-schema.js.map +1 -0
  54. package/dist/init.d.ts +15 -1
  55. package/dist/init.d.ts.map +1 -1
  56. package/dist/init.js +884 -261
  57. package/dist/init.js.map +1 -1
  58. package/dist/lark-cli-error.d.ts +47 -0
  59. package/dist/lark-cli-error.d.ts.map +1 -0
  60. package/dist/lark-cli-error.js +201 -0
  61. package/dist/lark-cli-error.js.map +1 -0
  62. package/dist/lark-cli.d.ts +1 -1
  63. package/dist/lark-cli.d.ts.map +1 -1
  64. package/dist/lark-cli.js +78 -18
  65. package/dist/lark-cli.js.map +1 -1
  66. package/dist/mutation-schema.d.ts +146 -0
  67. package/dist/mutation-schema.d.ts.map +1 -0
  68. package/dist/mutation-schema.js +153 -0
  69. package/dist/mutation-schema.js.map +1 -0
  70. package/dist/mutation.d.ts +10 -0
  71. package/dist/mutation.d.ts.map +1 -0
  72. package/dist/mutation.js +131 -0
  73. package/dist/mutation.js.map +1 -0
  74. package/dist/npm.d.ts +1 -0
  75. package/dist/npm.d.ts.map +1 -1
  76. package/dist/npm.js +2 -2
  77. package/dist/npm.js.map +1 -1
  78. package/dist/preview-receipt.d.ts.map +1 -1
  79. package/dist/preview-receipt.js +15 -5
  80. package/dist/preview-receipt.js.map +1 -1
  81. package/dist/process.d.ts +3 -1
  82. package/dist/process.d.ts.map +1 -1
  83. package/dist/process.js +16 -4
  84. package/dist/process.js.map +1 -1
  85. package/dist/program.d.ts.map +1 -1
  86. package/dist/program.js +246 -42
  87. package/dist/program.js.map +1 -1
  88. package/dist/project-build.d.ts +25 -0
  89. package/dist/project-build.d.ts.map +1 -0
  90. package/dist/project-build.js +229 -0
  91. package/dist/project-build.js.map +1 -0
  92. package/dist/project-context.d.ts +344 -0
  93. package/dist/project-context.d.ts.map +1 -0
  94. package/dist/project-context.js +485 -0
  95. package/dist/project-context.js.map +1 -0
  96. package/dist/project-layout.d.ts +5 -0
  97. package/dist/project-layout.d.ts.map +1 -0
  98. package/dist/project-layout.js +24 -0
  99. package/dist/project-layout.js.map +1 -0
  100. package/dist/public-sql-error.d.ts +25 -0
  101. package/dist/public-sql-error.d.ts.map +1 -0
  102. package/dist/public-sql-error.js +65 -0
  103. package/dist/public-sql-error.js.map +1 -0
  104. package/dist/pull.d.ts +1 -0
  105. package/dist/pull.d.ts.map +1 -1
  106. package/dist/pull.js +67 -15
  107. package/dist/pull.js.map +1 -1
  108. package/dist/site-readme.d.ts +3 -0
  109. package/dist/site-readme.d.ts.map +1 -0
  110. package/dist/site-readme.js +83 -0
  111. package/dist/site-readme.js.map +1 -0
  112. package/dist/workspace-fingerprint.d.ts +21 -1
  113. package/dist/workspace-fingerprint.d.ts.map +1 -1
  114. package/dist/workspace-fingerprint.js +81 -2
  115. package/dist/workspace-fingerprint.js.map +1 -1
  116. package/package.json +2 -1
  117. package/skills/lark-base-database/SKILL.md +86 -52
  118. package/skills/lark-base-database/references/ai-function.md +479 -0
  119. package/skills/lark-base-database/references/base-capability-routing.md +7 -7
  120. package/skills/lark-base-database/references/base-sql-errors.generated.md +403 -0
  121. package/skills/lark-base-database/references/database-function-limitations.md +29 -0
  122. package/skills/lark-base-database/references/database-function.md +323 -0
  123. package/skills/lark-base-database/references/development.md +166 -33
  124. package/skills/lark-base-database/references/environment-variables.md +55 -0
  125. package/skills/lark-base-database/references/faas.md +46 -53
  126. package/skills/lark-base-database/references/file-transfer.md +25 -25
  127. package/skills/lark-base-database/references/frontend-design-guide.md +151 -65
  128. package/skills/lark-base-database/references/mutations.md +295 -0
  129. package/skills/lark-base-database/references/notifications.md +9 -9
  130. package/skills/lark-base-database/references/permission-design.md +24 -24
  131. package/skills/lark-base-database/references/release.md +57 -30
  132. package/skills/lark-base-database/references/style-guide.md +10 -67
  133. package/skills/lark-base-database/references/verification.md +19 -13
  134. package/skills/lark-base-database/references/workspace.md +57 -17
  135. package/templates/database-starter/README.md +34 -4
  136. package/templates/database-starter/database-functions/README.md +8 -0
  137. package/templates/database-starter/gitignore +7 -0
  138. package/templates/database-starter/mutations/README.md +19 -0
  139. package/templates/database-starter/page/README.md +25 -19
  140. package/templates/database-starter/page/package-lock.json +8 -16
  141. package/templates/database-starter/page/package.json +2 -3
  142. package/templates/database-starter/page/src/App.tsx +23 -3
  143. package/templates/database-starter/page/src/components/UserSelectorEditor/index.tsx +32 -4
  144. package/templates/database-starter/page/src/components/UserSelectorPanel/index.tsx +104 -34
  145. package/templates/database-starter/page/src/components/UserSelectorPanel/user-search.test.ts +55 -11
  146. package/templates/database-starter/page/src/components/UserSelectorPanel/user-search.ts +68 -25
  147. package/templates/database-starter/page/src/components/database-runtime-ready.tsx +1 -1
  148. package/templates/database-starter/page/src/components/database-user-menu/index.tsx +4 -7
  149. package/templates/database-starter/page/src/components/feedback/page-state.tsx +40 -5
  150. package/templates/database-starter/page/src/components/floating-button/assets/edit-active.svg +3 -0
  151. package/templates/database-starter/page/src/components/floating-button/assets/edit-default.svg +3 -0
  152. package/templates/database-starter/page/src/components/floating-button/assets/toast-close.svg +3 -0
  153. package/templates/database-starter/page/src/components/floating-button/assets/toast-info.svg +5 -0
  154. package/templates/database-starter/page/src/components/floating-button/assets/tooltip-caret.svg +3 -0
  155. package/templates/database-starter/page/src/components/floating-button/index.tsx +193 -113
  156. package/templates/database-starter/page/src/components/floating-button/utils/project-meta.test.ts +107 -37
  157. package/templates/database-starter/page/src/components/floating-button/utils/project-meta.ts +13 -50
  158. package/templates/database-starter/page/src/components/ui-language.test.tsx +101 -0
  159. package/templates/database-starter/page/src/examples/ai-function/ImageTaskOperation.tsx +202 -0
  160. package/templates/database-starter/page/src/examples/ai-function/README.md +14 -0
  161. package/templates/database-starter/page/src/examples/ai-function/TextStreamOperation.tsx +184 -0
  162. package/templates/database-starter/page/src/examples/ai-function/ai-copy.ts +173 -0
  163. package/templates/database-starter/page/src/examples/ai-function/ai-delta.test.ts +30 -0
  164. package/templates/database-starter/page/src/examples/ai-function/ai-delta.ts +26 -0
  165. package/templates/database-starter/page/src/examples/ai-function/ai-language.test.tsx +86 -0
  166. package/templates/database-starter/page/src/examples/ai-function/ai-media-panel.test.ts +201 -0
  167. package/templates/database-starter/page/src/examples/ai-function/ai-media-panel.tsx +382 -0
  168. package/templates/database-starter/page/src/lib/attachment-image.tsx +144 -0
  169. package/templates/database-starter/page/src/lib/database-function-inputs.generated.ts +4 -0
  170. package/templates/database-starter/page/src/lib/database-runtime-attachment.test.ts +180 -76
  171. package/templates/database-starter/page/src/lib/database-runtime.test.ts +130 -10
  172. package/templates/database-starter/page/src/lib/database-runtime.ts +484 -51
  173. package/templates/database-starter/page/src/lib/date-time.ts +61 -0
  174. package/templates/database-starter/page/src/lib/ui-language.ts +86 -0
  175. package/templates/database-starter/page/src/main.tsx +1 -1
  176. package/templates/database-starter/page/tsconfig.json +2 -2
  177. package/templates/database-starter/page/vite.config.ts +1 -2
  178. package/templates/database-starter/prettierignore +1 -1
  179. package/templates/database-starter/server-functions/README.md +2 -2
  180. package/templates/database-starter/server-functions/package-lock.json +4 -4
  181. package/templates/database-starter/server-functions/package.json +1 -1
  182. package/dist/agent-guide.d.ts +0 -19
  183. package/dist/agent-guide.d.ts.map +0 -1
  184. package/dist/agent-guide.js +0 -31
  185. package/dist/agent-guide.js.map +0 -1
  186. package/dist/database-skill.d.ts +0 -24
  187. package/dist/database-skill.d.ts.map +0 -1
  188. package/dist/database-skill.js +0 -148
  189. package/dist/database-skill.js.map +0 -1
  190. package/skills/lark-base-database/references/base-sql-unsupported.md +0 -42
  191. package/skills/lark-base-database/references/database-capability-skill.md +0 -40
  192. package/skills/lark-base-database/references/sql-function.md +0 -289
  193. package/templates/database-capability/SKILL.md.tmpl +0 -58
  194. package/templates/database-starter/page/src/components/floating-button/chatbot-entry.test.ts +0 -23
  195. package/templates/database-starter/page/src/components/floating-button/chatbot-entry.tsx +0 -873
  196. package/templates/database-starter/page/src/components/floating-button/local-chat-panel.test.ts +0 -25
  197. package/templates/database-starter/page/src/components/floating-button/local-chat-panel.tsx +0 -197
  198. package/templates/database-starter/page/src/components/floating-button/utils/env.ts +0 -18
  199. package/templates/database-starter/sql/README.md +0 -8
@@ -0,0 +1,323 @@
1
+ # Database Function 专项
2
+
3
+ Database Function 通过固定查询语句读取 Base 数据,定义位于 `database-functions/<functionId>/`。
4
+
5
+ Database Function 只支持本文明确列出的只读查询语法和函数,不从通用关系型数据库的语法或函数推断其能力。
6
+
7
+ ## 阅读路径
8
+
9
+ 1. [Catalog](#catalog)
10
+ 2. [查询类型与结果值](#查询类型与结果值)
11
+ 3. [Function-文件与参数](#function-文件与参数)
12
+ 4. [查询约束](#查询约束)
13
+ 5. [调用与结果](#调用与结果)
14
+ 6. [校验](#校验)
15
+
16
+ ## Catalog
17
+
18
+ `catalog.json` 是已绑定 Base 的查询 schema。查询语句中的表名、字段名、OPTION 值和关联关系只从这里读取:
19
+
20
+ ```json
21
+ {
22
+ "tables": [
23
+ {
24
+ "baseToken": "basxxx",
25
+ "id": "tblTasks",
26
+ "name": "任务",
27
+ "fields": {
28
+ "状态": {
29
+ "id": "fldStatus",
30
+ "name": "状态",
31
+ "baseType": "SingleSelect",
32
+ "sqlType": {
33
+ "kind": "OPTION",
34
+ "options": [{ "id": "optTodo", "name": "待处理" }]
35
+ },
36
+ "nullable": true
37
+ },
38
+ "主客户": {
39
+ "id": "fldPrimaryCustomer",
40
+ "name": "主客户",
41
+ "baseType": "Link",
42
+ "sqlType": { "kind": "TEXT" },
43
+ "nullable": true,
44
+ "reference": {
45
+ "baseToken": "basxxx",
46
+ "tableId": "tblCustomers"
47
+ }
48
+ },
49
+ "协作客户": {
50
+ "id": "fldCustomers",
51
+ "name": "协作客户",
52
+ "baseType": "DuplexLink",
53
+ "sqlType": {
54
+ "kind": "ARRAY",
55
+ "element": { "kind": "TEXT" }
56
+ },
57
+ "nullable": true,
58
+ "reference": {
59
+ "baseToken": "basxxx",
60
+ "tableId": "tblCustomers"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ]
66
+ }
67
+ ```
68
+
69
+ - 查询语句使用 `tables[].name` 和 `fields[key].name`;中文、空格或标点名称用双引号。
70
+ - `fields[key].sqlType` 决定可查询类型;`UNSUPPORTED` 字段不查询。
71
+ - OPTION 是 `{id, name}` UDT。按展示名筛选时显式写 `(option_field).name`,按稳定标识筛选时显式写 `(option_field).id`;不要把整个 OPTION 与 TEXT 隐式比较。
72
+ - `ARRAY` 的元素类型在 `sqlType.element`。
73
+ - `Link` / `DuplexLink` 的单向或双向语义不决定 SQL 是否为数组;以 `sqlType` 为准:单值关联是 `TEXT`,多值关联是 `ARRAY<TEXT>`。不要只凭 `baseType`、字段名或关联方向猜单多值。
74
+ - 关联目标由 `reference` 定位,JOIN 使用目标表名和其 `rec_id`。单值关联使用 `link_field = target.rec_id`,多值关联使用 `link_field @> ARRAY[target.rec_id]`。
75
+ - 每张表自动提供 `rec_id`,类型为 `TEXT`。Base、table、field ID 不写进查询语句。
76
+
77
+ ## 查询类型与结果值
78
+
79
+ `sqlType` 是递归结构:标量为 `{"kind":"TEXT"}`,数组为 `{"kind":"ARRAY","element":{...}}`,OPTION 额外带 `options`。查询返回的行会序列化成普通 JSON:
80
+
81
+ | 查询类型 | ResultJSON 中的值 |
82
+ |---|---|
83
+ | `TEXT` | string |
84
+ | `OPTION` | object;`{id, name}` |
85
+ | `INT` | JavaScript safe integer number |
86
+ | `DOUBLE` | number |
87
+ | `BOOL` | boolean |
88
+ | `TIMESTAMP` | Base 执行时区下的无偏移时间字符串;固定为 `YYYY-MM-DD HH:mm:ss.SSS` |
89
+ | `INTERVAL` | 以毫秒为单位的固定时长字符串;值为 `1` / `-1` 时用 `millisecond`,其余用 `milliseconds`(聚合结果可含小数) |
90
+ | `AUTONUMBER` | object;`{sequence, number}`,`.sequence` 用于数值排序/计算,`.number` 用于展示 |
91
+ | `USER` | object;`{id, name}`,查询读取 `.id`、`.name` |
92
+ | `CHAT` | object;`{id, name}`,查询读取 `.id`、`.name` |
93
+ | `LOCATION` | object;`{address, lng, lat}` |
94
+ | `URL` | object;`{text, link}`,比较或投影文本时显式读取 `.text` 或 `.link` |
95
+ | `FILE` | object;`{token, name, mime_type, size}`,其中 `token` 对应附件的 `attachmentToken`、`size` 为整数;展示走[文件传输](file-transfer.md) |
96
+ | `ARRAY<T>` | `T[]`;例如 `OPTION[]` 的元素为 `{id, name}` |
97
+ | nullable field | 原类型或 `null` |
98
+
99
+ 执行时区按显式请求时区、Base 设置、Base 默认 `Asia/Shanghai` 的优先级确定;未设置时区的 Base 也按默认时区解析 TIMESTAMP 参数并渲染返回值。`TO_CHAR` 和时间投影使用同一生效时区。
100
+
101
+ 日期表单回填与时间展示按[日期与时区](development.md#日期与时区)处理。
102
+
103
+ ## Function 文件与参数
104
+
105
+ ```text
106
+ database-functions/<functionId>/
107
+ ├── main.sql
108
+ └── meta.json
109
+ ```
110
+
111
+ `main.sql` 只包含一条固定 `SELECT`。`meta.json` 包含非空 `name`、可选 `description` 和参数声明。
112
+
113
+ 查询使用位置参数 `$1`、`$2`……;`meta.params` 按相同顺序声明:
114
+
115
+ ```sql
116
+ SELECT rec_id AS record_id, "任务名称" AS title, ("状态").name AS status
117
+ FROM "任务"
118
+ WHERE "任务名称" LIKE $1 AND ("状态").name = $2
119
+ ORDER BY rec_id DESC
120
+ LIMIT $3
121
+ ```
122
+
123
+ ```json
124
+ {
125
+ "name": "搜索任务",
126
+ "description": "按关键词和状态搜索任务",
127
+ "meta": {
128
+ "params": [
129
+ { "kind": "TEXT", "description": "$1,已包含 % 的关键词" },
130
+ { "kind": "TEXT", "description": "$2,任务状态选项名" },
131
+ { "kind": "INT", "description": "$3,已由调用方限制上限的返回条数" }
132
+ ]
133
+ }
134
+ }
135
+ ```
136
+
137
+ `$n`、`meta.params[n-1]` 和调用 `params[n-1]` 必须连续、一一对应。每项只写 `kind` 和可选的 `description`。
138
+
139
+ 参数 `kind` 只使用 `TEXT`、`INT`、`DOUBLE`、`BOOL`、`TIMESTAMP`、`INTERVAL`,或为其中任一类型追加一个 `[]` 表示一维数组,例如 `TEXT[]`。标量、整个数组及数组元素都可传 `null`;不支持多维数组,也不接受 `OPTION`、`USER`、`CHAT`、`FILE`、`LOCATION`、`URL`、`AUTONUMBER` 等对象类型。SingleSelect 的动态展示名参数仍声明为 `TEXT`,但 SQL 必须显式写 `(option_field).name = $n`;按 ID 筛选则写 `(option_field).id = $n`。
140
+
141
+ `TIMESTAMP` 参数接受 RFC 3339 字符串、Base 执行时区下的无偏移 `YYYY-MM-DD HH:mm:ss.SSS` 字符串,或 JavaScript safe integer 范围内的 Unix epoch 毫秒整数。无偏移字符串遇到夏令时不存在或重复的当地时间会被拒绝,此时改传带偏移的 RFC 3339 或 epoch 毫秒。`INTERVAL` 参数只接受一个数字和一个单位组成的固定时长字符串。单位支持 `ms`、`millisecond(s)`、`second(s)`、`sec(s)`、`minute(s)`、`min(s)`、`hour(s)`、`day(s)`、`week(s)`,不区分大小写;数字可带符号或小数,但结果必须能无损换算为整数毫秒,例如 `250 ms`、`1.5 seconds`、`2 hours`。不接受复合形式 `1 day 02:03:04`、时钟形式 `02:03:04`,也不支持月、年等日历单位。数组参数的每个非 `null` 元素遵循相同规则。`INT` 必须处于 JavaScript safe integer 范围。CLI 不做类型推断,以 `meta.params[].kind` 为唯一声明。
142
+
143
+ `INT` 参数可用于支持集内的数值表达式,也可用于 `LIMIT` 或 `OFFSET`。页面输入不能直接决定返回规模;确需动态条数时,先在 Page/FaaS 校验为非负整数并限制最大值,再传入参数。正常列表优先使用代码中固定的有界 `LIMIT`。
144
+
145
+ 按文本数组参数筛选标量文本字段,使用 `= ANY($1)`:
146
+
147
+ ```sql
148
+ SELECT f.rec_id AS record_id, f."分组" AS group_name
149
+ FROM "跟进记录" AS f
150
+ WHERE f."分组" = ANY($1)
151
+ LIMIT 30
152
+ ```
153
+
154
+ 对应参数声明为 `{"kind":"TEXT[]"}`,例如调用 `params: [["重点", "普通"]]`。本例左侧为 `TEXT`,右侧为一维 `TEXT[]` 位置参数;其他形态按[生成参考](base-sql-errors.generated.md)判定。空数组不会选中记录。数组含 `null` 时不保证 PostgreSQL 三值语义,具体结果与预处理规则见[NULL 与数值边界](database-function-limitations.md#null-与数值边界)。成员判断与数组展开的区别及修法以[生成参考](base-sql-errors.generated.md)为准。
155
+
156
+ 无参数 Function 可以省略 `meta` 或 `meta.params`;归档统一补成 `{"params":[]}`。
157
+
158
+ Database Function 的 `inputSchema` 由 CLI 根据参数顺序与 kind 生成固定长度数组 Schema,不在 Database Function 的 `meta.json` 中手写。FaaS 则必须显式声明,见[FaaS 开发](faas.md#faas-开发)。
159
+
160
+ ## 查询约束
161
+
162
+ 只编写只读 `SELECT`,页面输入只能进入位置参数。具体能力、拒绝条件与修法以[目录生成的错误与替代写法](base-sql-errors.generated.md)为准;生成原则见[能力边界](database-function-limitations.md#查询生成边界)。
163
+
164
+ - 每条查询的最终查询计划必须引用至少一个 Catalog 中的真实表,用于 Project 执行时选择可路由的表依赖。根 `FROM` 可以是带别名的 `FROM (SELECT ...) AS alias` 派生表,但该派生表或它依赖的 CTE 必须读取真实表。
165
+ - 默认逐列投影并使用 `AS snake_case`,保持输出 Schema 稳定;连接的每一侧保持为一个表或带别名的派生表。常规生成优先简单连接、顶层查询和带别名的派生表,复杂组合依据生成参考及验证结果决定。
166
+ - 子查询优先作为带别名的 `FROM` 派生表使用,外层通过输出列名引用结果。需要其他形态时先核对业务语义与生成参考,不把默认生成偏好当作支持清单。
167
+ - 列表使用有界 `LIMIT` 和稳定排序;排序值可能重复时最后按 `rec_id` 排序。不把截断结果的前端分页视为完整分页或全量导出。
168
+ - 搜索写 `field LIKE $1`,由参数携带 `%`。
169
+ - 当前访问者使用 `CURRENT_USER_ID()`,不由页面传入用户 ID。权限关系的实现边界按[权限设计](permission-design.md#读路径database-function-就是策略面)。
170
+ - UDT 列成员使用 PostgreSQL 复合类型括号语法:无表别名写 `("负责人").id`、`("链接").link`,带表别名写 `(t."负责人").id`。FILE 的成员为 `.token/.name/.mime_type/.size`;逐元素读取后的 range alias 成员写 `u.id`。
171
+ - 集合字段的 PostgreSQL 数组运算符含义为:`@>`(包含)、`<@`(被包含)、`&&`(有交集)。先用 Catalog 的 `sqlType` 区分标量与集合,再按生成参考判断操作是否适用;展开集合前确认业务是否允许改变主表行数。
172
+ - 时间条件使用 Catalog 中确认为 `TIMESTAMP` 的字段和 `TIMESTAMP` 位置参数;当前时间使用 `CURRENT_TIMESTAMP`。查询结果固定为 Base 执行时区下的 `YYYY-MM-DD HH:mm:ss.SSS` 字符串。`EXTRACT(EPOCH FROM timestamp_expr)` 返回保留小数秒的 `DOUBLE`;明确只需整数秒时再写 `::bigint`。
173
+ - 窗口、聚合、字符串、日期和数值计算先明确结果契约,再查[目录生成的修法与已验证 SQL](base-sql-errors.generated.md)。默认采用满足前提的形式;例外是没有等价 SQL 或改写改变所需语义,此时统一按[SQL 外实现放在哪](../SKILL.md#sql-外实现放在哪)选择实现位置。
174
+
175
+ 字段类型和成员来自 Catalog,输入按本节参数协议声明;适用的字段操作与修复路径查[生成参考](base-sql-errors.generated.md),而不是从字段名称推断。示例演示参数和结果消费,不作为完整能力清单。
176
+
177
+ 条件聚合示例:
178
+
179
+ ```sql
180
+ SELECT
181
+ COUNT(s.rec_id) AS submitted_count,
182
+ SUM(CASE WHEN s."是否通过" = TRUE THEN 1.0 ELSE 0.0 END) AS passed_count,
183
+ ROUND(AVG(CASE WHEN s."得分" IS NULL THEN 0.0 ELSE s."得分" END)::numeric, 1) AS average_score
184
+ FROM "在线考试系统 - 答卷记录" AS s
185
+ ```
186
+
187
+ 路由表依赖:
188
+
189
+ ```sql
190
+ -- 错误:只读取派生常量,没有 Catalog 真实表依赖
191
+ SELECT anchor.ok AS ok
192
+ FROM (SELECT TRUE AS ok) AS anchor
193
+ LIMIT 1
194
+ ```
195
+
196
+ ```sql
197
+ -- 正确:从真实表读取或聚合,让执行器可以选择路由表
198
+ SELECT COUNT(t.rec_id) AS task_count
199
+ FROM "任务" AS t
200
+ ```
201
+
202
+ 如果业务只是返回前端常量、默认配置或入参回显,不创建 Database Function;直接在 Page 状态或 FaaS handler 中构造结果。只有结果必须由 Base 数据决定时才编写查询语句,并选择业务相关的真实表作为查询锚点。
203
+
204
+ 状态排序等固定映射也按上述写法:
205
+
206
+ ```sql
207
+ CASE
208
+ WHEN (c."客户状态").name = '线索' THEN 1
209
+ WHEN (c."客户状态").name = '潜在客户' THEN 2
210
+ WHEN (c."客户状态").name = '意向客户' THEN 3
211
+ WHEN (c."客户状态").name = '成交客户' THEN 4
212
+ WHEN (c."客户状态").name = '流失' THEN 5
213
+ ELSE 6
214
+ END AS sort_order
215
+ ```
216
+
217
+ 集合筛选:
218
+
219
+ ```sql
220
+ SELECT f.rec_id AS followup_id, f."跟进时间" AS followed_at
221
+ FROM "跟进记录" AS f
222
+ WHERE f."关联线索" @> ARRAY[$1]
223
+ ORDER BY f."跟进时间" DESC, f.rec_id DESC
224
+ LIMIT 30
225
+ ```
226
+
227
+ 单值关联按 record ID 标量筛选:
228
+
229
+ ```sql
230
+ SELECT o.rec_id AS order_id, o."订单号" AS order_no
231
+ FROM "订单" AS o
232
+ WHERE o."主客户" = $1
233
+ ORDER BY o."订单号" DESC, o.rec_id DESC
234
+ LIMIT 30
235
+ ```
236
+
237
+ 读取关联表字段且需要保留主表空关联行时,`LEFT JOIN` 真实目标表,不要 `LEFT JOIN UNNEST`:
238
+
239
+ ```sql
240
+ SELECT
241
+ o.rec_id AS order_id,
242
+ o."订单号" AS order_no,
243
+ c."客户名称" AS customer_name
244
+ FROM "订单" AS o
245
+ LEFT JOIN "客户" AS c ON o."客户" @> ARRAY[c.rec_id]
246
+ WHERE o."订单号" LIKE $1
247
+ ORDER BY o."订单号" DESC, o.rec_id DESC
248
+ LIMIT 20
249
+ ```
250
+
251
+ 上例是多值关联。若 Catalog 中关联字段为 `TEXT`,左连接改用标量等值:
252
+
253
+ ```sql
254
+ SELECT
255
+ o.rec_id AS order_id,
256
+ o."订单号" AS order_no,
257
+ c."客户名称" AS customer_name
258
+ FROM "订单" AS o
259
+ LEFT JOIN "客户" AS c ON o."主客户" = c.rec_id
260
+ ORDER BY o."订单号" DESC, o.rec_id DESC
261
+ LIMIT 20
262
+ ```
263
+
264
+ 如果没有真实目标表可 JOIN,又需要保留空集合主表行,先核对[生成参考中的语义前提](base-sql-errors.generated.md);没有等价形式时按[SQL 外实现选择原则](../SKILL.md#sql-外实现放在哪)处理。拆分后的输入若在开始时已经确定,应并发调用。
265
+
266
+ 搜索、筛选、关联和聚合尽量在查询中完成。没有关系、不同步消费或需要隔离失败语义的结果集可以拆分;拆分后的输入若在开始时已经确定,应并发调用。
267
+
268
+ ## 调用与结果
269
+
270
+ Database Function 的业务参数在两条链路上都是数组,但调用入口不同。Page 使用 starter 的 runtime client,不传 projectId:
271
+
272
+ ```ts
273
+ import { invokeDatabaseFunction } from '../lib/database-runtime'
274
+
275
+ await invokeDatabaseFunction<{ rows: unknown[] }>(
276
+ 'searchTasks',
277
+ ['%评审%', '待处理', 20],
278
+ )
279
+ ```
280
+
281
+ FaaS handler 内部使用 context,projectId 为当前应用的工程常量:
282
+
283
+ ```ts
284
+ await context.invokeFunction({
285
+ projectId: PROJECT_ID,
286
+ functionId: 'searchTasks',
287
+ params: ['%评审%', '待处理', 20],
288
+ })
289
+ ```
290
+
291
+ 公开 HTTP body:
292
+
293
+ ```json
294
+ { "params": ["%评审%", "待处理", 20] }
295
+ ```
296
+
297
+ 返回是 ResultJSON,不是 rows 数组:
298
+
299
+ ```json
300
+ {
301
+ "data": {
302
+ "rows": [
303
+ {
304
+ "record_id": "recxxx",
305
+ "title": "准备评审",
306
+ "status": "待处理",
307
+ "due_at": "2026-07-18 10:00:00.000"
308
+ }
309
+ ],
310
+ "columns": [],
311
+ "row_count": 1,
312
+ "log_id": "..."
313
+ },
314
+ "code": 0,
315
+ "msg": "success"
316
+ }
317
+ ```
318
+
319
+ 先检查 `code===0` 和 `data`,再验证 `data.rows` 以及每一行的数据结构。
320
+
321
+ ## 校验
322
+
323
+ `base-database-cli deploy` 统一调用服务端查询校验。编译成功仅证明编译阶段;实际读取按[发布与交付](release.md)或用户要求的[专项验收](verification.md)执行。失败时保留具体错误节点和 LogID,按 Catalog、参数与支持边界修复,不猜测响应 envelope 绕过。
@@ -1,51 +1,94 @@
1
1
  # Page 开发与 Function 调用
2
2
 
3
- 实现或修改 Page 的运行时接入时读取。FaaS 新增或修改时,还须在 meta.json 显式声明 inputSchema,见[FaaS 开发](faas.md#faas-开发)。SQL 方言见[SQL Function](sql-function.md),服务端 handler 与记录写入见[FaaS 协议](faas.md),设计原则见[工程质量](style-guide.md)。
3
+ 实现或修改 Page 的运行时接入时读取。FaaS 新增或修改时,还须在 meta.json 显式声明 inputSchema,见[FaaS 开发](faas.md#faas-开发)。基础单表写入与批量调用见[Mutation Function](mutations.md)。查询语法见[Database Function](database-function.md),服务端 handler 与记录写入见[FaaS 协议](faas.md),设计原则见[工程质量](style-guide.md)。
4
4
 
5
5
  ## 工程结构
6
6
 
7
7
  ```text
8
8
  project/
9
9
  ├── page/
10
- ├── sql/<functionId>/{main.sql,meta.json}
10
+ ├── database-functions/<functionId>/{main.sql,meta.json}
11
+ ├── mutations/<functionId>/{spec.json,meta.json}
11
12
  └── server-functions/functions/<functionId>/{index.ts,meta.json}
12
13
  ```
13
14
 
14
- SQL 与 FaaS 的 `functionId` 在 应用 内全局唯一,匹配 `/^[A-Za-z0-9_-]+$/`;SQL ID 长度为 1~64。`functionId` 会作为 URL 路径段参与运行态调用,除该字符集外不接受其它字符。不同 FaaS Function 目录独立构建,不跨目录 import。Page 和 `server-functions/` 分别维护自己的 npm 工程与 `package-lock.json`。
15
+ Database Function、Mutation 与 FaaS 的 `functionId` 在应用内全局唯一,匹配 `/^[A-Za-z0-9_-]+$/`;Database Function ID 长度为 1~64。`functionId` 会作为 URL 路径段参与运行态调用,除该字符集外不接受其它字符。不同 FaaS Function 目录独立构建,不跨目录 import。Page 和 `server-functions/` 分别维护自己的 npm 工程与 `package-lock.json`。
16
+
17
+ ## 第三方依赖策略
18
+
19
+ 为 Page 或 FaaS 新增、升级第三方 npm 依赖前,先查询当前应用的服务端策略。路径参数 `<app-token>` 是当前应用的 appToken,从工程的 `base-database.jsonc` 读取(不是 Base token):
20
+
21
+ ```bash
22
+ base-database-cli lark-cli -- api GET /open-apis/base/v3/sites/<app-token>/get --params '{"withDependencyPolicy":true}' --json
23
+ ```
24
+
25
+ 响应中的 `data.dependencyPolicy` 是唯一策略事实源,字段形如:
26
+
27
+ ```json
28
+ {
29
+ "restrictedProtocols": ["file", "git"],
30
+ "rejectLocalPaths": true,
31
+ "rejectScpGitSyntax": true,
32
+ "rejectHostedGitShorthand": true,
33
+ "allowedValues": ["https://registry[.]example[.]com/.*[.]tgz"]
34
+ }
35
+ ```
36
+
37
+ 普通 npm registry 版本、semver、`npm:` alias 和服务端未限制的协议默认可用。四个限制字段分别控制:`restrictedProtocols` 列出禁用的 URL 协议,如 `file:../pkg`、`git+https://example.com/pkg.git`;`rejectLocalPaths` 拒绝本地路径,如 `../pkg`、`/tmp/pkg`;`rejectScpGitSyntax` 拒绝 SCP 风格 Git 地址,如 `git@example.com:team/pkg.git`;`rejectHostedGitShorthand` 拒绝 hosted-git shorthand,如 `github:team/pkg`、`team/pkg`。
38
+
39
+ `allowedValues` 的每一项都是 RE2 语法的正则表达式;受限来源只有在原始 dependency value(未 trim、未改大小写)被其中某项**完整匹配**时才可用。不能按包名、域名、前缀或子串推断放行,也不能把 `allowedValues` 解释成 npm 包名白名单。固定字符串里的 `.`、`+`、`?` 等正则元字符必须写成 `[.]`、`\+`、`[?]` 等转义形式。
40
+
41
+ 策略查询失败、字段缺失或类型错误,或 `allowedValues` 含非法正则时,停止添加依赖,保留错误信息与 LogID 并通过支持渠道反馈;不用内置列表或历史策略继续。`deploy` 会在安装依赖前再次查询策略,递归校验最终制品范围内的全部 `package.json`;本地预检通过不替代上传接口的最终校验。
15
42
 
16
43
  ## Function 调用契约
17
44
 
18
- ABI 是 Page、应用 loader 与 Function 之间固定的数据边界。Page 侧两类 Function 都通过 `invokeDatabaseFunction(functionId, params)` 调用,但 `params` 类型不同:
45
+ Page 与 Function 之间使用固定的调用契约。Page 侧三类 Function 都通过 `invokeDatabaseFunction(functionId, params)` 调用,但 `params` 类型不同:
19
46
 
20
47
  `functionId` 使用源码目录名,是工程常量。应用 token 由 `page/src/lib/database-runtime.ts` 从产物托管路径解析,不由业务代码传入;FaaS 内部的 `context.invokeFunction` 仍使用 `{projectId,functionId,params}`,两者不要混用。
21
48
 
22
- | Function | `params` | 实现 | 结果 |
23
- |---|---|---|---|
24
- | SQL | 按 `$1`、`$2` 排列的数组 | `main.sql` + `meta.json` | 一层 `{data:{rows},code,msg}` |
25
- | FaaS | 业务对象 | `index.ts` + `meta.json` | 一层 `{data,code,msg}`,`data` 是 handler 业务数据 |
49
+ | Function | `params` | 实现 | 结果 |
50
+ | ---------------------- | ------------------------------------------ | ------------------------ | ------------------------------------------------------------------------------------ |
51
+ | Database Function | 按 `$1`、`$2` 排列的数组 | `main.sql` + `meta.json` | 一层 `{data:{rows},code,msg}` |
52
+ | Mutation | 声明顺序的位置数组;多组参数用批量 helper | `spec.json` + `meta.json` | 一层 `{data:{record_ids,records?,record_not_found?,ignored_fields?},code,msg}` |
53
+ | FaaS | 业务对象 | `index.ts` + `meta.json` | 一层 `{data,code,msg}`,`data` 是 handler 业务数据 |
26
54
 
27
- SQL 示例:
55
+ Database Function 查询示例:
28
56
 
29
57
  ```ts
30
- await invokeDatabaseFunction<{ rows: unknown[] }>('listTasks', ['待处理'])
58
+ await invokeDatabaseFunction<{ rows: unknown[] }>('listTasks', ['待处理']);
31
59
  ```
32
60
 
61
+ Mutation 一组参数使用 `invokeDatabaseFunction(functionId, params)`,每组操作一条记录;多组参数使用 `invokeDatabaseMutationBatch(functionId, parameterGroups)`,包装成 `{batch:[{params:[...]}]}`,不能与顶层 params 混用。两个 helper 均从 `page/src/lib/database-runtime.ts` 导出。`code=0` 仍须检查 `data.record_not_found/ignored_fields`,见[调用与结果](mutations.md#调用与结果)。
62
+
33
63
  FaaS 创建操作示例:
34
64
 
35
65
  ```ts
36
66
  await invokeDatabaseFunction<{ created: boolean | null }>('createTask', {
37
67
  title: '准备评审',
38
68
  status: '待处理',
39
- })
69
+ });
40
70
  ```
41
71
 
72
+ `base-database-cli check --json` 与 Preview `deploy` 会先校验本地 Function metadata,再把 Database Function 的
73
+ `meta.params` 和 FaaS `inputSchema` 生成到
74
+ `page/src/lib/database-function-inputs.generated.ts`,随后才编译 Page。不要手改该文件;新增或修改
75
+ Function 后运行 `check`,让 `functionId`、查询参数顺序以及 FaaS 必填字段和枚举值在 Page
76
+ 编译时保持一致。动态字符串不能作为 `functionId` 绕过这份本地契约。
77
+
78
+ metadata 只声明输入,因此生成文件只约束调用参数。调用结果仍由 Page 按 operation 定义
79
+ `invokeDatabaseFunction<TData>` 的 `TData`,并在运行时校验真实响应;CLI 不根据 handler
80
+ 实现或查询投影猜测返回类型。生成类型覆盖标量类型、对象属性、必填项、枚举、数组元素和
81
+ `prefixItems` 位置,以及 Database Function 的固定参数 tuple;`minimum`、`maximum`、`minLength`、
82
+ `maxLength`、非固定数组的 `minItems` / `maxItems` 和对象精确性仍由 Function 运行时校验;
83
+ 数组的元素类型和 `prefixItems` 位置仍保留在生成类型中。TypeScript 类型不能替代输入解析。
84
+
42
85
  `invokeDatabaseFunction` 调用统一网关的 `POST /space/api/bitable/site/v1/:token/invoke_function/:function_id`,由 BaseApp 父页面以同源身份代发;应用 token 与环境由 `src/lib/database-runtime.ts` 统一解析,`env` 走 query,请求体固定为 `{"params": <上述 params>}`。返回统一包裹结构 `{code,msg,data}`,页面使用当前登录用户身份,不保存也不传递任何凭证。
43
86
 
44
87
  ## Page 调用
45
88
 
46
- 默认 Page 的 `page/index.html` 需要把 `<title>` 设置为本次生成的 应用 名称,不留空。
89
+ 默认 Page 的 `page/index.html` 需要把 `<title>` 设置为本次生成的应用名称,不留空。
47
90
 
48
- 默认 Page 必须引用并渲染 `page/src/components/floating-button/index.tsx` 中的 `FloatingButton` 组件,并显式传入 `language`。该组件自行解析应用 token,不接受 `projectId`。悬浮控件提供「编辑应用」和 Chatbot 入口。
91
+ 默认 Page 必须引用并渲染 `page/src/components/floating-button/index.tsx` 中的 `FloatingButton` 组件。该组件自行解析应用 token,不接受 `projectId`。悬浮控件仅在正式环境向服务端判定有管理权限的访问者提供「编辑应用」入口。
49
92
 
50
93
  当前账号信息(头像、姓名、组织、退出登录)由**应用自己的导航**承载,用 `page/src/components/database-user-menu/index.tsx` 的 `DatabaseUserMenu`。账号菜单位于应用导航区域,例如顶栏右上角或侧边栏底部:
51
94
 
@@ -54,13 +97,13 @@ await invokeDatabaseFunction<{ created: boolean | null }>('createTask', {
54
97
  <header className="flex items-center justify-between ...">
55
98
  <h1>设备台账</h1>
56
99
  {/* 判据:员工 / 部门 / 领用归还属于组织内多角色协作 */}
57
- <DatabaseUserMenu language="zh" audience="enterprise" />
100
+ <DatabaseUserMenu audience="enterprise" />
58
101
  </header>
59
102
 
60
103
  // 侧边栏左下角
61
104
  <aside className="flex flex-col ...">
62
105
  <nav className="flex-1">…</nav>
63
- <DatabaseUserMenu language="zh" audience="enterprise" align="left" side="up" />
106
+ <DatabaseUserMenu audience="enterprise" align="left" side="up" />
64
107
  </aside>
65
108
  ```
66
109
 
@@ -71,36 +114,126 @@ await invokeDatabaseFunction<{ created: boolean | null }>('createTask', {
71
114
  `page/src/main.tsx` 已在首屏挂载后通过 `DatabaseRuntimeReady` 上报 `siteRuntime.ready()`,并在启动失败时上报 `reportLoadFailed`。宿主从握手完成起 10 秒内等不到 ready 会展示加载失败态,因此不要把 ready 上报移到数据加载完成之后,也不要删除该组件。
72
115
 
73
116
  涉及人员搜索或人员选择的页面,优先引用 `page/src/components/UserSelectorEditor` 和 `page/src/components/UserSelectorPanel` 等 `page/src/components/` 下的人员组件;只有现有组件无法满足交互契约时才做局部扩展。
74
- 选人组件返回的 `User.user_id` 是 Base 人员单元格使用的内部用户 ID;写入人员字段时固定传 `[{"id": user.user_id}]`。`User.open_id` 只是可选的开放平台标识,不得填入 应用记录 写入 payload。
117
+ 这些一方组件以及 `DatabaseUserMenu`、`FloatingButton`、`PageState`、`AttachmentImage` 内置中文、英文、日文固定文案,不依赖 Starling。组件统一按宿主 Runtime Context 的 locale → 浏览器语言 → 英语选择文案,无需 Page 传入或写死语言;宿主切换语言后,组件随重新加载的页面重新解析。Page 自己编写的业务文案仍由应用负责。
118
+ 选人组件返回的 `User.user_id` 是 Base 人员单元格使用的内部用户 ID。Mutation 的 `USER_ID` 参数传该 ID 字符串,`list:true` 时传 ID 数组;FaaS 下游人员单元格传 `[{"id": user.user_id}]`。`User.open_id` 只是可选的开放平台标识,不得填入应用记录写入 payload。
75
119
 
76
120
  业务请求集中在 operation/runtime client,不散落在组件中:
77
121
 
78
122
  ```ts
79
- import { invokeDatabaseFunction } from '../lib/database-runtime'
123
+ import { invokeDatabaseFunction } from '../lib/database-runtime';
80
124
 
81
- interface SqlRows<TRow> {
82
- rows: TRow[]
83
- columns?: unknown[]
84
- row_count?: number
85
- log_id?: string
125
+ interface DatabaseRows<TRow> {
126
+ rows: TRow[];
127
+ columns?: unknown[];
128
+ row_count?: number;
129
+ log_id?: string;
86
130
  }
87
131
  ```
88
132
 
89
- 浏览器 SDK 对 SQL 和 FaaS 都只返回一层 `{data,code,msg}`。`invokeDatabaseFunction<TData>` 的 `TData` 只描述当前 operation 约定的顶层 `data`:SQL 通常是 ResultJSON,FaaS 则以各 handler 的业务结果为准,不能统一假设存在 `recordId`。页面只检查一次顶层 `code`,再按 operation 契约校验 `data`;不得写成 `invokeDatabaseFunction<FaasHandlerResult<TData>>`,也不得对 `result.data` 二次解包。否则后端已经写入成功时,页面仍可能误报失败。
133
+ 浏览器 SDK 对 Database Function、Mutation 和 FaaS 都只返回一层 `{data,code,msg}`。`invokeDatabaseFunction<TData>` 的 `TData` 只描述当前 operation 约定的顶层 `data`:Database Function 通常是 ResultJSON,FaaS 则以各 handler 的业务结果为准,不能统一假设存在 `recordId`。页面只检查一次顶层 `code`,再按 operation 契约校验 `data`;不得写成 `invokeDatabaseFunction<FaasHandlerResult<TData>>`,也不得对 `result.data` 二次解包。否则后端已经写入成功时,页面仍可能误报失败。
90
134
 
91
135
  运行时业务数据在边界处校验,不能只依赖 TypeScript 泛型。`functionId` 和所有路由是工程常量;用户输入只进入已声明的业务参数。
92
136
 
93
- Page 的所有业务 HTTP 都经父页面代理,使用 `page/src/lib/database-runtime.ts` 导出的 `fetch` 或 `invokeHttp`,URL 必须是以单个 `/` 开头的同源相对路径;绝对 URL、跨域 URL、路径穿越以及 Cookie、Authorization、Host、Origin 等受限 Header 都会被父页面拒绝。请求体只支持字符串,4xx/5xx 会正常返回 `Response`,业务必须自行判断 `response.ok`。禁止直连业务域名,也不得把直连作为代理失败时的兜底。Page 发起的 Base 业务读取和写入仍分别走 SQL Function 与 FaaS。
137
+ Page 的所有业务 HTTP 都经父页面代理,使用 `page/src/lib/database-runtime.ts` 导出的 `fetch` 或 `invokeHttp`,URL 必须是以单个 `/` 开头的同源相对路径;绝对 URL、跨域 URL、路径穿越以及 Cookie、Authorization、Host、Origin 等受限 Header 都会被父页面拒绝。请求体只支持字符串,4xx/5xx 会正常返回 `Response`,业务必须自行判断 `response.ok`。禁止直连业务域名,也不得把直连作为代理失败时的兜底。Page 发起的 Base 业务读取和写入分别走 Database Function 与 Mutation/FaaS。
138
+
139
+ ## 日期与时区
140
+
141
+ Database Function 返回类型与 FaaS 写入格式分别以 [查询类型](database-function.md#查询类型与结果值) 和 [CellValue 协议](faas.md#base-读写协议)为准;Mutation 日期参数见 [Mutation 声明与参数](mutations.md#声明与参数)。转换时先区分字段语义:
142
+
143
+ | 业务语义 | 应用内表示 | 转换边界 |
144
+ | ------------------------------ | ---------------------- | ---------------------------------------------------------------------------- |
145
+ | 入库日、生日、到期日等日历日期 | `YYYY-MM-DD` 字符串 | 从 Base TIMESTAMP 回填时按该字段约定的业务时区取日期;表单编辑期间保持字符串 |
146
+ | 下单、上传等事件时刻 | Unix epoch 毫秒 number | 显示时按目标时区格式化;写入无偏移文本时按写入端解析时区格式化 |
147
+
148
+ 已有 Base 先通过 `lark-cli base +base-get --base-token <base-token>` 核对 `time_zone`;新建使用本次明确设置的 Base 时区。记录业务日期时区、写入解析时区及显示时区是否一致。`init` 的默认值、浏览器时区和 FaaS 进程时区都不能代替已有 Base 的真实配置;不要给所有应用固定加八小时。多 Base 应用分别确认。
149
+
150
+ `toISOString()` 输出 UTC。`toISOString().slice(0, 10)` 只适用于明确要求 UTC 日期的场景,不能用于其他业务时区的日期回填;同样,截掉 `Z` 再将 UTC 文本作为本地时间写入,会改变事件时刻。不要对已正确的 epoch 再加偏移后交给带 `timeZone` 的格式化器,否则会重复转换。
151
+
152
+ ### 转换与编辑示例
153
+
154
+ 工程 `page/src/lib/date-time.ts` 提供不依赖浏览器或 SDK 的纯函数:
155
+
156
+ - `formatDateInput(epochMs, timeZone)`:时间戳到该业务时区的 `YYYY-MM-DD`。
157
+ - `formatBaseDateTime(epochMs, timeZone)`:时间戳到该时区的 `YYYY-MM-DD HH:mm:ss`。
158
+ - `dateInputToBaseDateTime(value)`:校验日历日期并补 `00:00:00`,不先转成 UTC。
159
+
160
+ 旧工程没有 helper 时,从当前 CLI 的 starter 复制此文件并保持原工程其余接入。FaaS 需要使用时,将该无依赖文件复制到目标 `server-functions/functions/<functionId>/`,在同一 Function 内导入;不要跨 Function 或从 Page 目录运行时导入。
161
+
162
+ 以下 `BASE_TIME_ZONE` 是应用根据 Base 信息确定的常量,示例值不是默认值。不要让任意请求参数改变写入解释时区。
163
+
164
+ ```ts
165
+ // Page:时间戳只在回填边界转换,用户输入仍保持日历日期。
166
+ import { formatDateInput } from './lib/date-time';
167
+
168
+ const BASE_TIME_ZONE = 'Asia/Shanghai'; // 示例:已核对该 Base 的 time_zone
169
+
170
+ export function initialDate(stored: number | null): string {
171
+ return stored === null ? '' : formatDateInput(stored, BASE_TIME_ZONE);
172
+ }
173
+
174
+ export function editPayload(
175
+ publisher: string,
176
+ dateValue: string,
177
+ originalDate: string,
178
+ ) {
179
+ return {
180
+ publisher,
181
+ ...(dateValue !== originalDate ? { addedAt: dateValue } : {}),
182
+ };
183
+ }
184
+ ```
185
+
186
+ ```ts
187
+ // FaaS:同一 Function 目录内使用 helper,产生符合现有 CellValue 的文本。
188
+ import { dateInputToBaseDateTime, formatBaseDateTime } from './date-time';
189
+
190
+ const BASE_TIME_ZONE = 'Asia/Shanghai'; // 示例:已核对无偏移写入按此时区解释
191
+
192
+ export function updateFields(input: { publisher: string; addedAt?: string }) {
193
+ return {
194
+ 出版社: input.publisher,
195
+ ...(input.addedAt !== undefined
196
+ ? { 入库日期: dateInputToBaseDateTime(input.addedAt) }
197
+ : {}),
198
+ };
199
+ }
200
+
201
+ export function eventFields(now: number) {
202
+ return { 上传时间: formatBaseDateTime(now, BASE_TIME_ZONE) };
203
+ }
204
+ ```
205
+
206
+ 示例采用日期字段的增量更新,因此更新 Schema 中 `addedAt` 为可选项,创建 Schema 可继续要求必填。handler 按 [FaaS 输入契约](faas.md#faas-开发)校验并处理 helper 抛出的无效日期错误。清空日期需明确提交清空操作,不把空字符串解释成清空或今天。
207
+
208
+ 无偏移文本不能唯一表示夏令时回拨时的重复小时。业务需要这类精确时刻往返时,先确认写入端支持的无歧义表示,不靠固定偏移或 helper 宣称已保证。显示时区与写入时区不同可以成立,但界面必须清楚表达,不能将显示文本直接回写。
209
+
210
+ ### 日期与时间验证
211
+
212
+ - 纯日期:午夜前后、月末/年末、闰日;在不同浏览器/进程时区下回填相同业务日期。
213
+ - 编辑:只修改其他字段,写入 payload 不包含日期,底表原日期不变;主动修改日期后回读与选择一致;空值和 epoch `0` 分别处理。
214
+ - 事件:用已知 epoch 验证写入文本与目标时区,Database Function 回读后对应同一时刻(按接口精度比较),再分别验证页面和底表显示。覆盖实际使用时区的偏移和夏令时边界。
215
+
216
+ 历史数据偏移先核对原始值、字段语义与时区配置,再判断是否需要修复;不要因发现一处转换错误而批量平移历史时间。
217
+
218
+ ## AI 对话与调用
219
+
220
+ AI 调用使用 AI Function,不创建 FaaS 模型代理,也不直连模型域名。
221
+ Preview 使用草稿版本;Prod 使用正式发布的版本。Function 配置为 `disabled: true` 时不得调用。
222
+ 能力归属按运行场景判断:面向当前页面业务流程的生成、分类、总结和推荐使用 AI Function;Base 查询与写入仍分别使用 Database Function 和 FaaS。
223
+
224
+ Page 需要 AI 对话、生成、分类、总结、文生图或文生视频时,默认按 `functionKey` 使用 `page/src/lib/database-runtime.ts` 导出的 `callSiteAIFunction` / `invokeSiteAIFunctionStream`,不创建 FaaS 模型代理,也不直连模型域名。AI 能力被收口成一类版本化托管的 AI Function 配置;四类能力配置契约、CLI 建模/调试、计费与单流约束见 [AI Function](ai-function.md)。starter 的 `page/src/examples/ai-function/TextStreamOperation.tsx` 提供最小组件示例。搭建时可复用这些示例的界面及 `ai-copy.ts`,保留 `useUiLanguage()` 接入;内置标题、提示、按钮、任务状态和本地兜底错误自动跟随宿主 locale → 浏览器语言 → 英语。Page 显式传入的 `title` 由 Page 自行处理,服务端错误与 AI 生成内容保留原文。
225
+
226
+ AI 与 Base 数据组合时保持既有边界:先用 Database Function 读取,再由 Page 裁剪必要数据并拼入 `messages` 或 `variables`;需要把结果写回 Base 时另行调用 FaaS。AI 调用本身不经过 FaaS。
94
227
 
95
228
  ## 开发期与运行态输出
96
229
 
97
- | 调用层 | 判定方式 |
98
- | --- | --- |
99
- | lark-cli 默认 JSON 输出 | 先检查进程退出与外层 `ok`,再按该命令的 `data` 契约取值;错误诊断见 `error` |
100
- | Page `invokeDatabaseFunction` | 检查一次顶层 `code`,再校验 operation 的 `data` |
101
- | SQL Function | `data` 为 ResultJSON,包含 rows 等查询信息 |
102
- | FaaS handler | 返回 `{data,code,msg}`,data 为业务结果,不再由 Page 二次解包 |
103
- | FaaS 下游记录写入 | 检查下游 envelope 和各 batch 的结果语义,见[FaaS 协议](faas.md#应用记录-写入协议) |
230
+ | 调用层 | 判定方式 |
231
+ | ----------------------------- | --------------------------------------------------------------------------------- |
232
+ | lark-cli 默认 JSON 输出 | 先检查进程退出与外层 `ok`,再按该命令的 `data` 契约取值;错误诊断见 `error` |
233
+ | Page `invokeDatabaseFunction` | 检查一次顶层 `code`,再校验 operation 的 `data` |
234
+ | Database Function | `data` 为 ResultJSON,包含 rows 等查询信息 |
235
+ | FaaS handler | 返回 `{data,code,msg}`,data 为业务结果,不再由 Page 二次解包 |
236
+ | FaaS 下游记录写入 | 检查下游 envelope 和各 batch 的结果语义,见[FaaS 协议](faas.md#应用记录写入协议) |
104
237
 
105
238
  不要把 SDK 的 `code===0` 套在 CLI 外层,也不要将 CLI 外层的 data 当作 handler 业务数据。raw API 的命令封装与业务响应按当前 lark-cli reference 分别解析。
106
239
 
@@ -108,8 +241,8 @@ Page 的所有业务 HTTP 都经父页面代理,使用 `page/src/lib/database-
108
241
 
109
242
  实现前确定 operation 固定输入输出,按[工程质量](style-guide.md)检查远程调用依赖。新建示例数据在[发布阶段](release.md#从零建站示例数据验收)通过 CLI 写入真实 Base;不创建仅用于播种的 FaaS,也不在 Page 加载时自动写入。
110
243
 
111
- 保留 starter 的 `createArgusCsrfPlugin()` 配置;不手写 CSRF token。Page 的读取与写入分别通过 SQL/FaaS,自动化使用[原生能力](base-capability-routing.md),不从页面模拟后台定时任务。附件上传使用 `siteRuntime.uploadAttachment()`,缩略图与下载复用 starter 的 `buildAttachmentCoverUrl`、`fetchAttachmentDownloadUrl`;附件单元格追加/移除经 FaaS 调用增量路由,能力、并发和重试边界见[文件传输](file-transfer.md)。不能以 mock、未约定网关或旧列表整格覆盖代替。
244
+ Page 的读取与写入分别通过 Database Function 与 Mutation/FaaS,自动化使用[原生能力](base-capability-routing.md),不从页面模拟后台定时任务。附件上传使用 `siteRuntime.uploadAttachment()`,缩略图与下载复用 starter 的 `fetchAttachmentCover`、`fetchAttachmentDownloadUrl`;附件的写入选型、上传、展示与重试边界见[文件传输](file-transfer.md)。不能以 mock、未约定网关或旧列表整格覆盖代替。
112
245
 
113
- 修改或恢复旧 应用 时,不能假定工程已接入该插件。写请求出现 CSRF 相关失败时,按[旧工程与 CSRF 排查](verification.md#旧工程与-csrf-排查)核对插件、依赖和构建产物;缺失时按当前 starter 补齐,必要时升级 JSSDK,再重建发布。不得复制插件实现或手写 `x-secsdk-csrf-token`。
246
+ 修改或恢复旧应用时,按[旧工程与 CSRF 排查](verification.md#旧工程与-csrf-排查)迁移页面通信和构建配置。业务请求的身份与 CSRF 处理由宿主负责;Page 不注入 CSRF 脚本或手写 CSRF Header。
114
247
 
115
248
  实现完成后按[发布与交付](release.md)继续;用户明确要求运行态检查时按[诊断与验收](verification.md)执行。