@fui-org/fui-cli 0.2.0 → 0.3.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 (212) hide show
  1. package/README.md +13 -1
  2. package/dist/fui.js +160 -43
  3. package/package.json +4 -2
  4. package/skills/fui/SKILL.md +131 -66
  5. package/skills/fui-skill/README.md +112 -0
  6. package/skills/fui-skill/SKILL.md +269 -0
  7. package/skills/fui-skill/assets/projectdefaultstyle.css +518 -0
  8. package/skills/fui-skill/design-md/airbnb/DESIGN.md +545 -0
  9. package/skills/fui-skill/design-md/airbnb/README.md +5 -0
  10. package/skills/fui-skill/design-md/airtable/DESIGN.md +554 -0
  11. package/skills/fui-skill/design-md/airtable/README.md +5 -0
  12. package/skills/fui-skill/design-md/apple/DESIGN.md +562 -0
  13. package/skills/fui-skill/design-md/apple/README.md +5 -0
  14. package/skills/fui-skill/design-md/asu/DESIGN.md +179 -0
  15. package/skills/fui-skill/design-md/asu/README.md +107 -0
  16. package/skills/fui-skill/design-md/binance/DESIGN.md +634 -0
  17. package/skills/fui-skill/design-md/binance/README.md +5 -0
  18. package/skills/fui-skill/design-md/bmw/DESIGN.md +544 -0
  19. package/skills/fui-skill/design-md/bmw/README.md +5 -0
  20. package/skills/fui-skill/design-md/bmw-m/DESIGN.md +503 -0
  21. package/skills/fui-skill/design-md/bmw-m/README.md +5 -0
  22. package/skills/fui-skill/design-md/bugatti/DESIGN.md +454 -0
  23. package/skills/fui-skill/design-md/bugatti/README.md +5 -0
  24. package/skills/fui-skill/design-md/cal/DESIGN.md +542 -0
  25. package/skills/fui-skill/design-md/cal/README.md +5 -0
  26. package/skills/fui-skill/design-md/claude/DESIGN.md +589 -0
  27. package/skills/fui-skill/design-md/claude/README.md +5 -0
  28. package/skills/fui-skill/design-md/clay/DESIGN.md +541 -0
  29. package/skills/fui-skill/design-md/clay/README.md +5 -0
  30. package/skills/fui-skill/design-md/clickhouse/DESIGN.md +544 -0
  31. package/skills/fui-skill/design-md/clickhouse/README.md +5 -0
  32. package/skills/fui-skill/design-md/cohere/DESIGN.md +451 -0
  33. package/skills/fui-skill/design-md/cohere/README.md +5 -0
  34. package/skills/fui-skill/design-md/coinbase/DESIGN.md +570 -0
  35. package/skills/fui-skill/design-md/coinbase/README.md +5 -0
  36. package/skills/fui-skill/design-md/composio/DESIGN.md +506 -0
  37. package/skills/fui-skill/design-md/composio/README.md +5 -0
  38. package/skills/fui-skill/design-md/cursor/DESIGN.md +537 -0
  39. package/skills/fui-skill/design-md/cursor/README.md +5 -0
  40. package/skills/fui-skill/design-md/elevenlabs/DESIGN.md +504 -0
  41. package/skills/fui-skill/design-md/elevenlabs/README.md +5 -0
  42. package/skills/fui-skill/design-md/expo/DESIGN.md +526 -0
  43. package/skills/fui-skill/design-md/expo/README.md +5 -0
  44. package/skills/fui-skill/design-md/ferrari/DESIGN.md +531 -0
  45. package/skills/fui-skill/design-md/ferrari/README.md +5 -0
  46. package/skills/fui-skill/design-md/figma/DESIGN.md +578 -0
  47. package/skills/fui-skill/design-md/figma/README.md +5 -0
  48. package/skills/fui-skill/design-md/framer/DESIGN.md +544 -0
  49. package/skills/fui-skill/design-md/framer/README.md +5 -0
  50. package/skills/fui-skill/design-md/fui/DESIGN.md +532 -0
  51. package/skills/fui-skill/design-md/hashicorp/DESIGN.md +575 -0
  52. package/skills/fui-skill/design-md/hashicorp/README.md +5 -0
  53. package/skills/fui-skill/design-md/ibm/DESIGN.md +550 -0
  54. package/skills/fui-skill/design-md/ibm/README.md +5 -0
  55. package/skills/fui-skill/design-md/intercom/DESIGN.md +546 -0
  56. package/skills/fui-skill/design-md/intercom/README.md +5 -0
  57. package/skills/fui-skill/design-md/kraken/DESIGN.md +125 -0
  58. package/skills/fui-skill/design-md/kraken/README.md +5 -0
  59. package/skills/fui-skill/design-md/lamborghini/DESIGN.md +288 -0
  60. package/skills/fui-skill/design-md/lamborghini/README.md +5 -0
  61. package/skills/fui-skill/design-md/linear.app/DESIGN.md +548 -0
  62. package/skills/fui-skill/design-md/linear.app/README.md +5 -0
  63. package/skills/fui-skill/design-md/lovable/DESIGN.md +298 -0
  64. package/skills/fui-skill/design-md/lovable/README.md +5 -0
  65. package/skills/fui-skill/design-md/mastercard/DESIGN.md +365 -0
  66. package/skills/fui-skill/design-md/mastercard/README.md +5 -0
  67. package/skills/fui-skill/design-md/meta/DESIGN.md +683 -0
  68. package/skills/fui-skill/design-md/meta/README.md +5 -0
  69. package/skills/fui-skill/design-md/minimax/DESIGN.md +746 -0
  70. package/skills/fui-skill/design-md/minimax/README.md +5 -0
  71. package/skills/fui-skill/design-md/mintlify/DESIGN.md +852 -0
  72. package/skills/fui-skill/design-md/mintlify/README.md +5 -0
  73. package/skills/fui-skill/design-md/miro/DESIGN.md +825 -0
  74. package/skills/fui-skill/design-md/miro/README.md +5 -0
  75. package/skills/fui-skill/design-md/mistral.ai/DESIGN.md +773 -0
  76. package/skills/fui-skill/design-md/mistral.ai/README.md +5 -0
  77. package/skills/fui-skill/design-md/mongodb/DESIGN.md +767 -0
  78. package/skills/fui-skill/design-md/mongodb/README.md +5 -0
  79. package/skills/fui-skill/design-md/nike/DESIGN.md +575 -0
  80. package/skills/fui-skill/design-md/nike/README.md +5 -0
  81. package/skills/fui-skill/design-md/notion/DESIGN.md +821 -0
  82. package/skills/fui-skill/design-md/notion/README.md +5 -0
  83. package/skills/fui-skill/design-md/nvidia/DESIGN.md +640 -0
  84. package/skills/fui-skill/design-md/nvidia/README.md +5 -0
  85. package/skills/fui-skill/design-md/ollama/DESIGN.md +539 -0
  86. package/skills/fui-skill/design-md/ollama/README.md +5 -0
  87. package/skills/fui-skill/design-md/opencode.ai/DESIGN.md +521 -0
  88. package/skills/fui-skill/design-md/opencode.ai/README.md +5 -0
  89. package/skills/fui-skill/design-md/pinterest/DESIGN.md +597 -0
  90. package/skills/fui-skill/design-md/pinterest/README.md +5 -0
  91. package/skills/fui-skill/design-md/playstation/DESIGN.md +661 -0
  92. package/skills/fui-skill/design-md/playstation/README.md +5 -0
  93. package/skills/fui-skill/design-md/posthog/DESIGN.md +690 -0
  94. package/skills/fui-skill/design-md/posthog/README.md +5 -0
  95. package/skills/fui-skill/design-md/raycast/DESIGN.md +669 -0
  96. package/skills/fui-skill/design-md/raycast/README.md +5 -0
  97. package/skills/fui-skill/design-md/renault/DESIGN.md +589 -0
  98. package/skills/fui-skill/design-md/renault/README.md +5 -0
  99. package/skills/fui-skill/design-md/replicate/DESIGN.md +616 -0
  100. package/skills/fui-skill/design-md/replicate/README.md +5 -0
  101. package/skills/fui-skill/design-md/resend/DESIGN.md +585 -0
  102. package/skills/fui-skill/design-md/resend/README.md +5 -0
  103. package/skills/fui-skill/design-md/revolut/DESIGN.md +636 -0
  104. package/skills/fui-skill/design-md/revolut/README.md +5 -0
  105. package/skills/fui-skill/design-md/runwayml/DESIGN.md +244 -0
  106. package/skills/fui-skill/design-md/runwayml/README.md +5 -0
  107. package/skills/fui-skill/design-md/sanity/DESIGN.md +357 -0
  108. package/skills/fui-skill/design-md/sanity/README.md +5 -0
  109. package/skills/fui-skill/design-md/sentry/DESIGN.md +551 -0
  110. package/skills/fui-skill/design-md/sentry/README.md +5 -0
  111. package/skills/fui-skill/design-md/shopify/DESIGN.md +516 -0
  112. package/skills/fui-skill/design-md/shopify/README.md +5 -0
  113. package/skills/fui-skill/design-md/slack/DESIGN.md +482 -0
  114. package/skills/fui-skill/design-md/spacex/DESIGN.md +363 -0
  115. package/skills/fui-skill/design-md/spacex/README.md +5 -0
  116. package/skills/fui-skill/design-md/spotify/DESIGN.md +246 -0
  117. package/skills/fui-skill/design-md/spotify/README.md +5 -0
  118. package/skills/fui-skill/design-md/starbucks/DESIGN.md +580 -0
  119. package/skills/fui-skill/design-md/starbucks/README.md +5 -0
  120. package/skills/fui-skill/design-md/stripe/DESIGN.md +487 -0
  121. package/skills/fui-skill/design-md/stripe/README.md +5 -0
  122. package/skills/fui-skill/design-md/supabase/DESIGN.md +462 -0
  123. package/skills/fui-skill/design-md/supabase/README.md +5 -0
  124. package/skills/fui-skill/design-md/superhuman/DESIGN.md +448 -0
  125. package/skills/fui-skill/design-md/superhuman/README.md +5 -0
  126. package/skills/fui-skill/design-md/tesla/DESIGN.md +286 -0
  127. package/skills/fui-skill/design-md/tesla/README.md +5 -0
  128. package/skills/fui-skill/design-md/theverge/DESIGN.md +339 -0
  129. package/skills/fui-skill/design-md/theverge/README.md +5 -0
  130. package/skills/fui-skill/design-md/together.ai/DESIGN.md +633 -0
  131. package/skills/fui-skill/design-md/together.ai/README.md +5 -0
  132. package/skills/fui-skill/design-md/uber/DESIGN.md +636 -0
  133. package/skills/fui-skill/design-md/uber/README.md +5 -0
  134. package/skills/fui-skill/design-md/vercel/DESIGN.md +736 -0
  135. package/skills/fui-skill/design-md/vercel/README.md +5 -0
  136. package/skills/fui-skill/design-md/vodafone/DESIGN.md +538 -0
  137. package/skills/fui-skill/design-md/vodafone/README.md +5 -0
  138. package/skills/fui-skill/design-md/voltagent/DESIGN.md +521 -0
  139. package/skills/fui-skill/design-md/voltagent/README.md +5 -0
  140. package/skills/fui-skill/design-md/warp/DESIGN.md +526 -0
  141. package/skills/fui-skill/design-md/warp/README.md +5 -0
  142. package/skills/fui-skill/design-md/webflow/DESIGN.md +588 -0
  143. package/skills/fui-skill/design-md/webflow/README.md +5 -0
  144. package/skills/fui-skill/design-md/wired/DESIGN.md +497 -0
  145. package/skills/fui-skill/design-md/wired/README.md +5 -0
  146. package/skills/fui-skill/design-md/wise/DESIGN.md +544 -0
  147. package/skills/fui-skill/design-md/wise/README.md +5 -0
  148. package/skills/fui-skill/design-md/x.ai/DESIGN.md +465 -0
  149. package/skills/fui-skill/design-md/x.ai/README.md +5 -0
  150. package/skills/fui-skill/design-md/zapier/DESIGN.md +537 -0
  151. package/skills/fui-skill/design-md/zapier/README.md +5 -0
  152. package/skills/fui-skill/examples/component.vue +162 -0
  153. package/skills/fui-skill/examples/f-table-patterns.json +331 -0
  154. package/skills/fui-skill/examples/module-patterns.json +973 -0
  155. package/skills/fui-skill/examples/project-patterns.json +222 -0
  156. package/skills/fui-skill/metadata.json +75 -0
  157. package/skills/fui-skill/references/INDEX.md +144 -0
  158. package/skills/fui-skill/references/advanced-techniques.md +160 -0
  159. package/skills/fui-skill/references/coding-standards.md +112 -0
  160. package/skills/fui-skill/references/component-design.md +455 -0
  161. package/skills/fui-skill/references/component-quickref.md +77 -0
  162. package/skills/fui-skill/references/component-table.md +276 -0
  163. package/skills/fui-skill/references/components-dialog.md +192 -0
  164. package/skills/fui-skill/references/components-display.md +147 -0
  165. package/skills/fui-skill/references/components-echart.md +391 -0
  166. package/skills/fui-skill/references/components-input.md +359 -0
  167. package/skills/fui-skill/references/controls-patterns.md +847 -0
  168. package/skills/fui-skill/references/controls-styling-vocabulary.md +140 -0
  169. package/skills/fui-skill/references/db-table-design.md +77 -0
  170. package/skills/fui-skill/references/db-workflow.md +504 -0
  171. package/skills/fui-skill/references/default-function.md +415 -0
  172. package/skills/fui-skill/references/design-modes.md +85 -0
  173. package/skills/fui-skill/references/echart-templates.md +481 -0
  174. package/skills/fui-skill/references/fastproject.md +97 -0
  175. package/skills/fui-skill/references/fsheet.md +218 -0
  176. package/skills/fui-skill/references/fullstack-workflow.md +351 -0
  177. package/skills/fui-skill/references/module-data-patterns.md +126 -0
  178. package/skills/fui-skill/references/module-json-anatomy.md +137 -0
  179. package/skills/fui-skill/references/module-structure.md +260 -0
  180. package/skills/fui-skill/references/new-session.md +108 -0
  181. package/skills/fui-skill/references/pdfmake.md +60 -0
  182. package/skills/fui-skill/references/permission-system.md +169 -0
  183. package/skills/fui-skill/references/platform-architecture.md +294 -0
  184. package/skills/fui-skill/references/project-config.md +335 -0
  185. package/skills/fui-skill/references/project-provisioning.md +383 -0
  186. package/skills/fui-skill/references/script-map.md +296 -0
  187. package/skills/fui-skill/references/sql-clr-functions.md +224 -0
  188. package/skills/fui-skill/references/system-design.md +116 -0
  189. package/skills/fui-skill/references/tapi-file-api.md +191 -0
  190. package/skills/fui-skill/references/tapi-permission-patterns.md +158 -0
  191. package/skills/fui-skill/references/tapi-reference.md +549 -0
  192. package/skills/fui-skill/references/tools-registry.md +460 -0
  193. package/skills/fui-skill/references/ui-crosswindow-patterns.md +317 -0
  194. package/skills/fui-skill/references/ui-dialog-patterns.md +229 -0
  195. package/skills/fui-skill/references/ui-layout-patterns.md +176 -0
  196. package/skills/fui-skill/references/ui-patterns.md +315 -0
  197. package/skills/fui-skill/references/ui-screenshot-review.md +94 -0
  198. package/skills/fui-skill/references/ui-table-cell-patterns.md +316 -0
  199. package/skills/fui-skill/references/ui-templates.md +29 -0
  200. package/skills/fui-skill/references/verification.md +246 -0
  201. package/skills/fui-skill/references/watcher-patterns.md +196 -0
  202. package/skills/fui-skill/references/websocket-realtime.md +254 -0
  203. package/skills/fui-skill/scripts/component-3.0.js +2549 -0
  204. package/skills/fui-skill/scripts/component.js +3142 -0
  205. package/skills/fui-skill/scripts/componentTable-3.0.js +909 -0
  206. package/skills/fui-skill/scripts/componentTable.js +769 -0
  207. package/skills/fui-skill/scripts/defaultfunction-3.0.js +781 -0
  208. package/skills/fui-skill/scripts/defaultfunction.js +966 -0
  209. package/skills/fui-skill/scripts/fastproject-3.0.js +870 -0
  210. package/skills/fui-skill/scripts/fastproject.js +828 -0
  211. package/skills/fui-skill/scripts/fechart.js +890 -0
  212. package/skills/fui-skill/scripts/fsheet.js +1330 -0
@@ -0,0 +1,549 @@
1
+ # tAPI Reference — Transparent Query API (Core)
2
+
3
+ > File này sở hữu: **quy ước tAPI: đặt tên SP (`spAPI_*`/`spAPI_AUTH_*`/`spAPIFILE_*`), params, URL, response contract, route `/help`**. Quy trình deploy xem [db-workflow.md](db-workflow.md).
4
+
5
+ tAPI tự sinh API từ SQL Stored Procedures. Tên SP quyết định endpoint URL, kiểu xác thực, và quyền truy cập. Không cần viết controller hay route — chỉ cần đặt tên SP đúng quy tắc.
6
+
7
+ tAPI hỗ trợ cả **GET và POST** — không cần chỉ định HTTP method khi viết SP. FUI module.json tự quyết định method dựa trên cách gọi.
8
+
9
+ File này là **core**: naming, params, URL, response format, error handling, SP design rules, checklist. Hai chủ đề chuyên sâu đã tách ra — load thêm khi cần:
10
+
11
+ | Chủ đề | File |
12
+ |---|---|
13
+ | Kiểm tra quyền trong thân SP (5 pattern, `@sys_SystemRight`, `getSystemRight`) | [tapi-permission-patterns.md](tapi-permission-patterns.md) |
14
+ | File API — GET/UPLOAD, `spAPIFILE_`, `fileContent`, `tblFileData` | [tapi-file-api.md](tapi-file-api.md) |
15
+
16
+ > Nếu SP cần xử lý ảnh, gọi HTTP ra ngoài, gửi email, đọc/ghi file trên server, hoặc regex — kiểm tra xem DB đã có sẵn **SQL CLR function** (`httpCall`, `sendMail`, `ImageResize`, `FileWriter`, `RegexMatch`...) chưa trước khi viết logic thủ công. Xem [sql-clr-functions.md](sql-clr-functions.md).
17
+
18
+ ## 1. Quy tắc đặt tên SP
19
+
20
+ ### Cấu trúc tên
21
+
22
+ ```
23
+ spAPI_[AUTH_]FunctionName
24
+ ```
25
+
26
+ | Phần | Bắt buộc | Ý nghĩa |
27
+ |---|---|---|
28
+ | `spAPI_` | ✅ | Đánh dấu SP được phép gọi qua API. Thiếu prefix này → API không gọi được |
29
+ | `AUTH_` | ❌ | Không yêu cầu xác thực. Bỏ qua → bắt buộc phải có token hợp lệ |
30
+ | `FunctionName` | ✅ | Tên hàm API, dùng trong URL. Ví dụ: `MessageHome`, `UserSetting` |
31
+
32
+ **Ví dụ tên SP:**
33
+
34
+ ```sql
35
+ spAPI_MessageHome -- có auth
36
+ spAPI_AUTH_SelectChat -- không cần auth
37
+ ```
38
+
39
+ ### File API
40
+
41
+ ```
42
+ spAPIFILE_Document -- SP để GET file
43
+ spAPIFILE_UPLOAD_Document -- SP để nhận file upload
44
+ ```
45
+
46
+ > Chi tiết File API: xem [tapi-file-api.md](tapi-file-api.md).
47
+
48
+ ---
49
+
50
+ ## 2. Tham số SP
51
+
52
+ Ba loại tham số:
53
+
54
+ ### `@url1_`, `@url2_`, `@url3_`... — Route params
55
+
56
+ Giá trị lấy trực tiếp từ URL theo thứ tự:
57
+
58
+ ```
59
+ https://api.domain.vn/app/FunctionName/value1/value2
60
+ ```
61
+
62
+ ```sql
63
+ @url1_GroupID varchar(50), -- nhận "value1"
64
+ @url2_Mode varchar(50), -- nhận "value2"
65
+ ```
66
+
67
+ ### `@sys_` — System params (tAPI tự inject, không cần truyền từ client)
68
+
69
+ | Tham số | Kiểu | Mô tả |
70
+ |---|---|---|
71
+ | `@sys_UserID` | varchar(9) | Mã người dùng đã xác thực |
72
+ | `@sys_UserName` | varchar(50) | Tên đăng nhập |
73
+ | `@sys_GroupID` | int | Nhóm của user |
74
+ | `@sys_DepartmentID` | varchar(9) | Mã đơn vị |
75
+ | `@sys_SystemRight` | int | Quyền phân tầng (1=thấp, 9=cao) |
76
+ | `@sys_FunctionRight` | varchar(2000) | Quyền chức năng dạng `[AD][RPT][MGR]` |
77
+ | `@sys_SessionID` | varchar(50) | Mã phiên xác thực |
78
+ | `@sys_RawData` | nvarchar(max) | Toàn bộ JSON body từ client |
79
+ | `@sys_header_{TênHeader}` | varchar(n) | **Một HỌ tham số, không phải một tên cố định** — đọc bất kỳ HTTP header nào; `NULL` khi client không gửi header đó. Quy tắc đặt tên ở mục ngay dưới |
80
+
81
+ > **CRITICAL — Không truyền `@sys_` vào `IN` của module.json:**
82
+ > tAPI backend tự gán toàn bộ tham số `@sys_*` từ session đã xác thực. Nếu client truyền vào `IN`, giá trị sẽ bị **bỏ qua hoặc ghi đè** bởi server — truyền vào là thừa và có thể gây nhầm lẫn.
83
+ >
84
+ > ```json
85
+ > // ❌ SAI — không đưa sys_ vào IN
86
+ > "IN": { "ModuleID": "sModuleID", "sys_UserID": "vueData.user.UserID" }
87
+ >
88
+ > // ✅ ĐÚNG — chỉ truyền custom params
89
+ > "IN": { "ModuleID": "sModuleID" }
90
+ > ```
91
+ >
92
+ > `@sys_UserID`, `@sys_SystemRight`, `@sys_FunctionRight`... đều được tAPI tự điền — SP nhận đúng giá trị mà không cần client gửi.
93
+
94
+ #### `@sys_header_*` — đọc HTTP header: thay mỗi dấu `-` bằng `_`
95
+
96
+ Muốn đọc một HTTP header thì khai một tham số `@sys_header_{TênHeader}` — **bất kỳ header nào**, không
97
+ giới hạn trong một danh sách cho sẵn. Nhưng tên header HTTP chứa dấu `-` (`User-Agent`,
98
+ `Sec-Ch-Ua-Mobile`), mà **`-` không hợp lệ trong tên biến/tham số của SQL Server**. Viết thẳng tên
99
+ header vào là script không biên dịch được:
100
+
101
+ ```sql
102
+ -- ❌ SAI — lỗi cú pháp ngay lúc CREATE PROCEDURE, không phải lỗi runtime
103
+ @sys_header_User-Agent varchar(500)
104
+ ```
105
+
106
+ **Không có cách bọc nào cứu được.** T-SQL không cho phép tên tham số dạng delimited identifier: cả
107
+ `@[sys_header_User-Agent]` lẫn `@"sys_header_User-Agent"` đều không hợp lệ. Đừng mất thời gian thử —
108
+ chuyển tên là con đường **duy nhất**.
109
+
110
+ **Quy tắc chuyển: mỗi dấu `-` thành một dấu `_`.** Không bỏ bớt ký tự, không đổi hoa-thường, không ghép
111
+ các đoạn lại với nhau — đây là phép thay thế 1-đổi-1, nên nhìn tên tham số là đọc ngược ra tên header
112
+ ngay, không phải đoán ranh giới giữa các đoạn.
113
+
114
+ | HTTP header | Tham số trong SP |
115
+ |---|---|
116
+ | `User-Agent` | `@sys_header_User_Agent` |
117
+ | `Sec-Ch-Ua-Mobile` | `@sys_header_Sec_Ch_Ua_Mobile` |
118
+ | `X-Forwarded-For` | `@sys_header_X_Forwarded_For` |
119
+ | `Accept-Language` | `@sys_header_Accept_Language` |
120
+ | `Cookie` | `@sys_header_Cookie` — không có `-` thì giữ nguyên |
121
+
122
+ Chiều ngược lại (đọc SP có sẵn để biết nó lấy header nào): bỏ tiền tố `@sys_header_`, rồi đổi mọi `_`
123
+ còn lại thành `-` — `@sys_header_Accept_Language` → `Accept-Language`.
124
+
125
+ **Hoa-thường không quan trọng** — tAPI so khớp không phân biệt chữ hoa chữ thường, ở cả tiền tố lẫn tên
126
+ header: `@sys_header_User_Agent`, `@sys_Header_User_Agent`, `@sys_header_USER_AGENT` đều tra đúng một
127
+ header. (Đó là lý do `@sys_Header_RequestID` viết hoa chữ `H` trong bảng trên vẫn chạy.) Dù vậy **hãy
128
+ viết đúng hoa-thường như tên header chuẩn** (`User-Agent`, không `USER_AGENT`): người đọc SP nhận ra
129
+ ngay header nào, khỏi phải dịch. Thứ **bắt buộc** đúng vẫn chỉ là dấu `_` thay cho `-`.
130
+
131
+ ```sql
132
+ CREATE PROCEDURE spAPI_AuditLog_Insert
133
+ @Action nvarchar(100),
134
+ @sys_UserID varchar(9),
135
+ @sys_header_User_Agent nvarchar(500),
136
+ @sys_header_X_Forwarded_For varchar(200)
137
+ AS
138
+ BEGIN
139
+ -- ISNULL vì client không gửi header thì tham số là NULL, không phải chuỗi rỗng
140
+ INSERT INTO tblAuditLog (Action, UserID, UserAgent, ClientIP, CreateTime)
141
+ VALUES (@Action, @sys_UserID,
142
+ ISNULL(@sys_header_User_Agent, N'(không có)'),
143
+ ISNULL(@sys_header_X_Forwarded_For, ''),
144
+ GETDATE());
145
+ END
146
+ ```
147
+
148
+ Ba điều đi kèm, đừng bỏ qua:
149
+
150
+ - **Vẫn là `@sys_*`, nên vẫn theo mọi luật của họ đó:** tAPI tự điền, **không** đưa vào `IN` của
151
+ module.json, và **không được gán default value** (`= null` là lỗi cứng — `db_sp_deploy` chặn, xem mục
152
+ dưới).
153
+ - **Client không gửi header thì tham số là `NULL`** (không phải chuỗi rỗng) — và header vắng mặt là
154
+ chuyện bình thường, không phải ca hiếm: lời gọi từ script/server-to-server thường không có
155
+ `User-Agent`. `NULL` kéo theo hai hệ quả im lặng của T-SQL, bọc bằng `ISNULL(...)` ngay tại chỗ dùng:
156
+ - **So sánh ra UNKNOWN, không ra TRUE/FALSE.** `WHERE @sys_header_User_Agent <> 'bot'` **loại luôn**
157
+ dòng khi header vắng — ngược hẳn điều người viết mong đợi.
158
+ - **Nối chuỗi bằng `+` biến cả chuỗi thành `NULL`.** Một header vắng đủ để xoá trắng nguyên câu log.
159
+ - **Dữ liệu header do client tự khai, KHÔNG tin được.** `User-Agent`, `X-Forwarded-For` giả được hết —
160
+ dùng để ghi log/thống kê thì hợp lý, lấy làm căn cứ phân quyền thì không. Quyền lấy từ
161
+ `@sys_UserID`/`@sys_SystemRight` (tAPI dựng từ token đã xác thực), không lấy từ header.
162
+
163
+ Không chắc tAPI có nhận đúng tên mình vừa đặt không → `db_sp_help({ name })` in ra danh sách tham số
164
+ đầu vào mà tAPI thực sự thấy (xem §3.1).
165
+
166
+ ### Custom params — Tham số nghiệp vụ do lập trình viên định nghĩa
167
+
168
+ Tên tham số = tên field trong JSON body gửi lên:
169
+
170
+ ```sql
171
+ @StudentID varchar(9),
172
+ @SemesterID int,
173
+ @Keyword nvarchar(200)
174
+ ```
175
+
176
+ #### Ghép tham số là KHOAN DUNG — thiếu hay thừa đều không lỗi
177
+
178
+ Đây là hành vi nền tảng, quyết định cách đọc mọi mục còn lại của §2:
179
+
180
+ | Tình huống | tAPI làm gì |
181
+ |---|---|
182
+ | Body **thiếu** một khoá mà SP có tham số | truyền **`NULL`** cho tham số đó |
183
+ | Body **thừa** một khoá mà SP không có tham số | **bỏ qua im lặng** |
184
+
185
+ **Không có ca nào báo lỗi.** Gõ sai tên khoá trong `IN` của module.json (`StudentId` thay vì
186
+ `StudentID`) không làm request hỏng — SP chỉ nhận `NULL` và trả về kết quả rỗng hoặc sai, còn HTTP vẫn
187
+ 200. Đây là lớp lỗi hỏng-âm-thầm hay gặp nhất khi nối API.
188
+
189
+ Hai hệ quả phải nhớ:
190
+
191
+ - **Đổi/xoá tham số của SP là thay đổi contract KHÔNG phát tín hiệu.** Module cũ vẫn gửi khoá cũ, vẫn
192
+ chạy, chỉ là giá trị rơi vào hư không. Đừng trông chờ một lỗi để biết mình quên sửa nơi gọi — tự
193
+ soát, hoặc đối chiếu bằng `db_sp_help`.
194
+ - **`NULL` lan theo luật T-SQL**: `WHERE @x <> 'y'` thành UNKNOWN nên loại luôn dòng, và `+` nối chuỗi
195
+ ra `NULL`. Tham số có thể vắng thì bọc `ISNULL(...)` ngay tại chỗ dùng.
196
+
197
+ Kiểm tên khoá thật sự tới được SP: `db_sp_help({ name })` in danh sách tham số mà tAPI đang thấy
198
+ (xem §3.1).
199
+
200
+ > **CRITICAL — Không được gán giá trị mặc định cho bất kỳ tham số nào:**
201
+ > tAPI **luôn truyền đủ mọi tham số của SP** — khoá nào client không gửi thì nó truyền `NULL`. Nên
202
+ > `DEFAULT` trong khai báo SP **không bao giờ được dùng tới**: viết `@HeID int = 99` rồi không gửi
203
+ > `HeID` thì nhận `NULL`, không phải `99`. Default ở đây là một lời hứa luôn bị phá, và nó im lặng.
204
+ > Không gán `= value` hay `= null` cho bất kỳ tham số nào — kể cả `@sys_*`. **Đây không chỉ là quy ước**: `db_sp_deploy` chặn cứng (code-level, `detectDefaultApiParams()` trong `src/tools/db.ts`) mọi SP `spAPI_*`/`spAPIFILE_*` có tham số dạng này, và `db_sp_verify` cảnh báo sớm cùng lỗi trước cả khi deploy.
205
+ > Cần một giá trị mặc định thì đặt **trong thân SP**: `SET @HeID = ISNULL(@HeID, 99)`.
206
+ >
207
+ > ```sql
208
+ > -- ❌ SAI
209
+ > CREATE PROCEDURE spAPI_TS_Report
210
+ > @HeID int = 99,
211
+ > @NamHoc int = 2024,
212
+ > @sys_UserID varchar(9) = null,
213
+ > @sys_SystemRight int
214
+ >
215
+ > -- ✅ ĐÚNG
216
+ > CREATE PROCEDURE spAPI_TS_Report
217
+ > @HeID int,
218
+ > @NamHoc int,
219
+ > @sys_UserID varchar(9),
220
+ > @sys_SystemRight int
221
+ > ```
222
+
223
+ Client gửi: `POST /app/FunctionName` với body `{ "StudentID": "SV001", "SemesterID": 1 }`
224
+
225
+ ---
226
+
227
+ ## 3. URL gọi API
228
+
229
+ ```
230
+ {domain}/{apiName}/{FunctionName}/{url1}/{url2}...
231
+ {domain}/{apiName}/auth/{FunctionName}/{url1}/{url2}...
232
+ ```
233
+
234
+ | Phần | Ý nghĩa |
235
+ |---|---|
236
+ | `domain` | Domain API, ví dụ `https://api.example.vn` |
237
+ | `apiName` | Tên apiName của ứng dụng: `me`, `congvan`, `calen`... — cũng chính là **alias kết nối DB** trong workspace (`_db/{apiName}/`) |
238
+ | `auth` | Thêm segment này khi SP có `AUTH_` (không cần token) |
239
+ | `FunctionName` | Phần tên sau prefix của SP |
240
+ | `url1`, `url2`... | Route params tương ứng `@url1_`, `@url2_` |
241
+
242
+ **Một project có thể có NHIỀU `apiName`** — tức nhiều database, mỗi cái một alias. `"API": "/{alias}/Ten"`
243
+ trong module.json là thứ nói SP đó nằm ở database nào; MCP dùng đúng alias ấy làm tên thư mục
244
+ `_db/{alias}/` và làm tham số `db` của các DB tool (xem [db-workflow.md](db-workflow.md) §1, §4).
245
+
246
+ **Ví dụ:**
247
+
248
+ ```
249
+ SP: spAPI_AUTH_Select_Chat
250
+ URL: https://api.example.vn/me/auth/Select_Chat
251
+
252
+ SP: spAPI_GetStudentList
253
+ URL: https://api.example.vn/me/GetStudentList
254
+
255
+ SP: spAPI_GetStudentDetail (@url1_StudentID)
256
+ URL: https://api.example.vn/me/GetStudentDetail/SV001
257
+ ```
258
+
259
+ > **CRITICAL — lỗi ghép URL hay gặp: thiếu dấu `/` giữa `domain` và `apiName`.**
260
+ > `domain` KHÔNG BAO GIỜ tự có dấu `/` ở cuối — phải tự chèn đúng MỘT dấu `/` trước `apiName` khi ghép. Gõ tắt bằng tay rất dễ dính lỗi này vì mắt đọc lướt không phân biệt được đâu là phần domain, đâu là phần apiName trong một chuỗi liền.
261
+ >
262
+ > ```
263
+ > domain=tapi.lhu.edu.vn, apiName=ts, function=TS_Report_HeXetTuyenSelectAll
264
+ >
265
+ > ❌ SAI (thiếu /, "ts" bị nuốt vào domain): https://tapi.lhu.edu.vnts/TS_Report_HeXetTuyenSelectAll
266
+ > ✅ ĐÚNG: https://tapi.lhu.edu.vn/ts/TS_Report_HeXetTuyenSelectAll
267
+ > ```
268
+ >
269
+ > **Đừng tự ghép chuỗi bằng tay/trí nhớ.** `db_sp_verify` (dòng `Wiring URL: ...`) và `db_sp_help` đã tự tính URL đúng cho từng SP — copy nguyên văn dòng đó thay vì gõ lại.
270
+
271
+ ### 3.1 Cache tham số & route `/help`
272
+
273
+ tAPI **cache chữ ký tham số** (danh sách tên/kiểu param) của mỗi hàm sau lần gọi đầu tiên. Sau khi ALTER một SP `spAPI_*`/`spAPIFILE_*` để **thêm/bớt/đổi tên/đổi kiểu tham số**, endpoint thật vẫn phục vụ theo chữ ký **cũ** cho tới khi cache được xoá.
274
+
275
+ Route xoá cache = URL gọi API thật, chèn thêm `/help` ngay sau domain (trước `apiName`):
276
+
277
+ ```
278
+ {domain}/help/{apiName}/{FunctionName} ← xoá cache + trả về tham số của hàm
279
+ {domain}/help/{apiName}/auth/{FunctionName}
280
+ ```
281
+
282
+ Gọi route này có hai tác dụng đồng thời: **(a) xoá cache tham số** của hàm đó và **(b) trả về danh sách tham số đầu vào** — hữu ích để tra cứu trước khi wire `IN` vào module.json mà không chắc SP đang nhận params nào.
283
+
284
+ **Quy tắc bắt buộc — xoá cache sau mỗi lần ALTER/UPDATE SP:**
285
+ - Dùng `db_sp_deploy`: tool tự gọi `/help` xoá cache. **Gọi đúng một lần theo URL chuẩn; hỏng thì bỏ qua** — SP đã deploy xong, đây chỉ là dọn cache.
286
+ - Dùng `db_sql_execute_nonquery` để chạy `ALTER PROCEDURE` trực tiếp: **không có auto-clear** → **bắt buộc gọi `db_sp_help` thủ công** ngay sau khi ALTER thành công. Đây là ca duy nhất bắt buộc gọi tay.
287
+ - Không xoá cache → endpoint thật tiếp tục phục vụ theo chữ ký **cũ**, có thể gây lỗi tham số ngay cả khi SP đã đúng.
288
+ - **`/help` báo lỗi thì DỪNG, không thử URL biến thể.** Dạng URL (`/auth/` hay không) suy ra từ tên SP, không phải thứ để đoán; thử qua lại chỉ tốn lượt mà không đổi kết quả. Chỉ quay lại `db_sp_help` khi endpoint thật thực sự còn nhận tham số cũ.
289
+
290
+ ---
291
+
292
+ ## 4. Format dữ liệu trả về
293
+
294
+ ### 4.1 Một SELECT — trả về array
295
+
296
+ ```sql
297
+ SELECT StudentID, FullName, GPA FROM tblStudent WHERE ClassID = @ClassID
298
+ ```
299
+
300
+ ```json
301
+ {
302
+ "data": [
303
+ { "StudentID": "SV001", "FullName": "Nguyen Van A", "GPA": 3.5 },
304
+ { "StudentID": "SV002", "FullName": "Tran Thi B", "GPA": 3.2 }
305
+ ]
306
+ }
307
+ ```
308
+
309
+ ### 4.2 Nhiều SELECT — trả về array of arrays
310
+
311
+ Mỗi `SELECT` trong SP tạo thành một mảng con trong `data`:
312
+
313
+ ```sql
314
+ SELECT TotalCount = COUNT(*) FROM tblStudent WHERE ClassID = @ClassID
315
+ SELECT StudentID, FullName FROM tblStudent WHERE ClassID = @ClassID
316
+ ```
317
+
318
+ ```json
319
+ {
320
+ "data": [
321
+ [{ "TotalCount": 42 }],
322
+ [
323
+ { "StudentID": "SV001", "FullName": "Nguyen Van A" },
324
+ { "StudentID": "SV002", "FullName": "Tran Thi B" }
325
+ ]
326
+ ]
327
+ }
328
+ ```
329
+
330
+ **Trong FUI module.json**, đọc từng mảng con qua index:
331
+ ```json
332
+ "OUT": "studentList",
333
+ "CALLBACK": { "EXE": "vueData.total = vueData.studentList[0][0].TotalCount; vueData.students = vueData.studentList[1]" }
334
+ ```
335
+
336
+ ### 4.3 `convert_to_object` — convert một dòng thành object
337
+
338
+ Dùng khi muốn trả về record dạng object thay vì array. Một SP có thể có **nhiều SELECT** dùng `convert_to_object` — tất cả được merge vào cùng một object response.
339
+
340
+ > ⚠️ **Quy tắc quan trọng**: Khi SELECT có `convert_to_object` **và** cột `json_data` chứa JSON string, SELECT đó chỉ được trả về **đúng 1 dòng**. Nếu nhiều hơn 1 dòng → tAPI trả về `{}` (object rỗng).
341
+
342
+ **Hai dạng giá trị:**
343
+ - `convert_to_object = 'key'` → tạo nested object với tên key đó
344
+ - `convert_to_object = ''` → merge thẳng vào root object
345
+
346
+ **Ví dụ 1 — Kết hợp named key và root merge:**
347
+
348
+ ```sql
349
+ SELECT convert_to_object = 'sinhvien',
350
+ sys_SystemRight = @sys_SystemRight,
351
+ sys_FunctionRight = @sys_FunctionRight
352
+
353
+ SELECT convert_to_object = '',
354
+ ngaythang = GETDATE(), count = 10
355
+ ```
356
+
357
+ ```json
358
+ {
359
+ "sinhvien": {
360
+ "sys_SystemRight": 9,
361
+ "sys_FunctionRight": "[11][3]"
362
+ },
363
+ "ngaythang": "2018-09-11T11:20:47.397",
364
+ "count": 10
365
+ }
366
+ ```
367
+
368
+ **Ví dụ 2 — `json_data` override field cùng tên:**
369
+
370
+ ```sql
371
+ SELECT convert_to_object = '',
372
+ ProjectName = N'abc',
373
+ stt = 123,
374
+ json_data = '{"ProjectName":"T-A-P-I","menu":[{"name":"Croatia","link":"vn"},{"name":"England","link":"/tuyensinh"}]}'
375
+ ```
376
+
377
+ ```json
378
+ {
379
+ "ProjectName": "T-A-P-I",
380
+ "stt": 123,
381
+ "menu": [
382
+ { "name": "Croatia", "link": "vn" },
383
+ { "name": "England", "link": "/tuyensinh" }
384
+ ]
385
+ }
386
+ ```
387
+
388
+ > `json_data` được parse và merge sau — nếu có field trùng tên với cột SQL (`ProjectName`), **giá trị từ `json_data` sẽ override**.
389
+
390
+ **Ví dụ 3 — Nhiều SELECT cùng `convert_to_object = ''` đều merge vào root:**
391
+
392
+ ```sql
393
+ SELECT convert_to_object = '',
394
+ ProjectName = N'abc', stt = 123,
395
+ json_data = '{"ProjectName":"T-A-P-I","menu":[{"name":"Croatia","link":"vn"},{"name":"England","link":"/tuyensinh"}]}'
396
+
397
+ SELECT convert_to_object = '',
398
+ TruyVan2 = N'Dữ liệu của truy vấn 2',
399
+ json_data = '{"TruyVan2_json_string": 99}'
400
+ ```
401
+
402
+ ```json
403
+ {
404
+ "ProjectName": "T-A-P-I",
405
+ "stt": 123,
406
+ "menu": [
407
+ { "name": "Croatia", "link": "vn" },
408
+ { "name": "England", "link": "/tuyensinh" }
409
+ ],
410
+ "TruyVan2": "Dữ liệu của truy vấn 2",
411
+ "TruyVan2_json_string": 99
412
+ }
413
+ ```
414
+
415
+ Tất cả SELECT trong SP đều merge vào **cùng 1 object** — không tạo array như SELECT thông thường.
416
+
417
+ ### 4.4 `[json_data:FieldName]` — Convert chuỗi JSON thành object
418
+
419
+ Khi cột trong bảng lưu chuỗi JSON và cần trả về dạng object (không phải string), dùng alias `[json_data:FieldName]`:
420
+
421
+ ```sql
422
+ SELECT PartID, PartName, [json_data:FileInfo] = FileInfo
423
+ FROM tblPart WHERE PartID = @url1_PartID
424
+ ```
425
+
426
+ tAPI tự động parse cột có alias `json_data:FieldName` thành nested JSON object trong response:
427
+
428
+ ```json
429
+ {
430
+ "PartID": "P001",
431
+ "PartName": "Tài liệu",
432
+ "FileInfo": { "fileName": "doc.pdf", "size": 1024 }
433
+ }
434
+ ```
435
+
436
+ Không cần parse thủ công ở client. Cột DB vẫn lưu dạng string — chỉ response được convert.
437
+
438
+ > Tên sau dấu `:` (`FileInfo`) là tên field trong response. Tên cột SQL (`FileInfo` trong `= FileInfo`) là tên cột thực trên bảng.
439
+
440
+ ---
441
+
442
+ ## 5. Xử lý lỗi trong SP
443
+
444
+ Tất cả lỗi dùng `RAISERROR` với severity **16** — không dùng severity khác.
445
+
446
+ ### Lỗi thông thường (validation, ràng buộc dữ liệu)
447
+
448
+ ```sql
449
+ RAISERROR(N'Dữ liệu không hợp lệ', 16, 1)
450
+ RETURN
451
+ ```
452
+
453
+ ```json
454
+ { "Message": "Dữ liệu không hợp lệ" }
455
+ ```
456
+
457
+ ### Lỗi Unauthorized (thiếu quyền) — prefix `[Unauthorized]` → HTTP 401
458
+
459
+ ```sql
460
+ IF @sys_SystemRight < 2
461
+ BEGIN
462
+ RAISERROR(N'[Unauthorized]Bạn không có quyền trên chức năng này.', 16, 1)
463
+ RETURN
464
+ END
465
+ ```
466
+
467
+ ```json
468
+ { "Message": "Bạn không có quyền trên chức năng này." }
469
+ ```
470
+
471
+ **Quy tắc bắt buộc:**
472
+ - Luôn dùng severity `16` — không dùng `11`, `14`, hay giá trị khác
473
+ - Luôn có `RETURN` ngay sau `RAISERROR` để dừng SP
474
+ - Prefix `N` bắt buộc cho Unicode tiếng Việt
475
+ - **Mọi lỗi liên quan đến quyền (thiếu `SystemRight`/`FunctionRight`) phải mở message bằng `[Unauthorized]`** — tAPI đọc chuỗi này để trả HTTP status `401` thay vì `200`/`500` mặc định; client (`errorMess()` trong `fastproject.js`) rẽ nhánh xử lý riêng theo status `401` (redirect login nếu message chứa "token", ngược lại hiện lỗi quyền). tAPI tự cắt `[Unauthorized]` khỏi `Message` trả về client — text người dùng thấy không có tiền tố này. Lỗi validation/ràng buộc dữ liệu thông thường (ví dụ trên) **không** dùng prefix này.
476
+
477
+ > **Kiểm tra quyền chi tiết (5 pattern, `getSystemRight`, thứ tự kiểm tra, quy ước message):** xem [tapi-permission-patterns.md](tapi-permission-patterns.md).
478
+
479
+ ---
480
+
481
+ ## 6. Quy tắc thiết kế SP tốt
482
+
483
+ ### Đặt tên SP
484
+
485
+ ```
486
+ spAPI_{Entity}{Action}
487
+ ```
488
+
489
+ | Action | Mô tả |
490
+ |---|---|
491
+ | `Select` / `List` | Truy vấn danh sách |
492
+ | `Get` | Lấy chi tiết một bản ghi |
493
+ | `Insert` / `Add` | Thêm mới |
494
+ | `Update` / `Edit` | Cập nhật |
495
+ | `Delete` / `Remove` | Xoá |
496
+ | `AUTH_Select` | Truy vấn công khai (không cần token) |
497
+
498
+ **Ví dụ chuẩn:**
499
+
500
+ ```sql
501
+ spAPI_StudentList -- GET danh sách sinh viên
502
+ spAPI_StudentGet -- GET chi tiết (url1 = StudentID)
503
+ spAPI_StudentInsert -- POST thêm mới
504
+ spAPI_StudentUpdate -- POST cập nhật
505
+ spAPI_StudentDelete -- POST xoá (url1 = StudentID)
506
+ spAPI_AUTH_CourseList -- Danh sách môn học công khai
507
+ ```
508
+
509
+ ### Wiring trong FUI module.json
510
+
511
+ ```json
512
+ "fetchStudents": {
513
+ "API": "/me/StudentList",
514
+ "IN": { "Keyword": "vueData.keyword", "ClassID": "vueData.selectedClass" },
515
+ "OUT": "studentList"
516
+ },
517
+ "addStudent": {
518
+ "API": "/me/StudentInsert",
519
+ "IN": {
520
+ "StudentID": "vueData.form.StudentID",
521
+ "FullName": "vueData.form.FullName",
522
+ "ClassID": "vueData.form.ClassID"
523
+ },
524
+ "CALLBACK": [ { "CALL": "vueData.fetchStudents" }, { "MESS": "Đã thêm thành công" } ]
525
+ },
526
+ "deleteStudent": {
527
+ "API": "/me/StudentDelete/`{{vueData.selectedID}}",
528
+ "CONFIRM": "Bạn có chắc muốn xoá?",
529
+ "CALLBACK": { "CALL": "vueData.fetchStudents" }
530
+ }
531
+ ```
532
+
533
+ ---
534
+
535
+ ## 7. Checklist khi thiết kế SP
536
+
537
+ 1. Prefix `spAPI_` bắt buộc — thiếu là API không gọi được
538
+ 2. Thêm `AUTH_` nếu endpoint công khai (login page, lookup data)
539
+ 3. `@sys_UserID` luôn có trong SP có ghi dữ liệu (audit trail)
540
+ 4. Không dùng `SELECT *` — liệt kê cột cụ thể
541
+ 5. Kiểm tra quyền ở đầu SP nếu cần, dùng `RAISERROR` + `RETURN` — xem [tapi-permission-patterns.md](tapi-permission-patterns.md)
542
+ 6. Nhiều SELECT → client đọc theo index `data[0]`, `data[1]`
543
+ 7. Một record duy nhất → dùng `convert_to_object` thay vì `data[0][0]`
544
+ 8. **Không viết `GRANT EXECUTE` vào file .sql** — thân `CREATE PROCEDURE` kéo dài tới hết batch và tAPI không hỗ trợ `GO`, nên câu GRANT đặt sau `END` bị **nuốt vào thân SP**: nó chạy mỗi lần gọi SP chứ không cấp quyền lúc deploy, và không lỗi nào nổi lên. `db_sp_deploy` tự cấp quyền cho procedure mới rồi xác nhận lại bằng `sys.database_permissions` — đọc dòng `✓`/`❗` trong kết quả (chi tiết: [verification.md](verification.md#security))
545
+ 9. **Không đưa tham số `sys_*` vào `IN` của module.json** — tAPI tự inject, truyền vào là thừa
546
+ 9b. **Đọc HTTP header** → `@sys_header_{TênHeader}` với **mỗi dấu `-` đổi thành `_`** (`User-Agent` → `@sys_header_User_Agent`). Giữ nguyên dấu `-` là **lỗi cú pháp lúc CREATE PROCEDURE**, và không bọc ngoặc vuông để né được — xem §2. Header do client tự khai nên chỉ dùng để log, không dùng để gác quyền
547
+ 9c. **Ghép tham số không bao giờ báo lỗi**: khoá thiếu → `NULL`, khoá thừa → bỏ qua. Gõ sai tên khoá trong `IN` cho ra HTTP 200 + dữ liệu rỗng/sai, không cho ra lỗi — đối chiếu tên bằng `db_sp_help` thay vì chờ hệ thống báo. Cũng vì vậy `DEFAULT` trong khai báo SP vô tác dụng (tAPI luôn truyền `NULL`); mặc định phải đặt trong thân: `SET @x = ISNULL(@x, <giá trị>)`
548
+ 10. **File GET/UPLOAD**: prefix `spAPIFILE_`, cột binary alias `fileContent`, params `@sys_FileContent`/`@sys_FileName`, bảng chuẩn `tblFileData` — xem [tapi-file-api.md](tapi-file-api.md). Không tự suy từ training data.
549
+ 11. **Mọi ALTER/UPDATE SP đã deploy** (đổi tham số, đổi logic, hay bất kỳ thay đổi nào) → **BẮT BUỘC xoá cache tAPI** qua `db_sp_help` sau khi deploy — nếu không endpoint thật vẫn phục vụ theo chữ ký cũ. `db_sp_deploy` tự làm (một lần, hỏng thì bỏ qua); chỉ khi ALTER qua `db_sql_execute_nonquery` mới phải gọi `db_sp_help` thủ công.