@fui-org/fui-cli 1.3.2 → 2.0.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 (111) hide show
  1. package/README.md +19 -3
  2. package/dist/fui-bmg5pnmq.js +379 -0
  3. package/dist/fui.js +1 -1
  4. package/package.json +3 -6
  5. package/skills/fui/SKILL.md +9 -41
  6. package/skills/fui-skill/SKILL.md +95 -225
  7. package/skills/fui-skill/assets/projectdefaultstyle-3.0.css +555 -0
  8. package/skills/fui-skill/assets/projectdefaultstyle.css +207 -235
  9. package/skills/fui-skill/references/INDEX.md +105 -137
  10. package/skills/fui-skill/references/advanced-techniques.md +76 -68
  11. package/skills/fui-skill/references/coding-standards.md +56 -56
  12. package/skills/fui-skill/references/component-design.md +166 -173
  13. package/skills/fui-skill/references/component-quickref.md +61 -60
  14. package/skills/fui-skill/references/component-table.md +128 -117
  15. package/skills/fui-skill/references/components-dialog.md +55 -56
  16. package/skills/fui-skill/references/components-display.md +24 -30
  17. package/skills/fui-skill/references/components-echart.md +186 -261
  18. package/skills/fui-skill/references/components-input.md +72 -96
  19. package/skills/fui-skill/references/controls-patterns.md +196 -342
  20. package/skills/fui-skill/references/controls-styling-vocabulary.md +130 -97
  21. package/skills/fui-skill/references/db-table-design.md +24 -28
  22. package/skills/fui-skill/references/db-workflow.md +191 -390
  23. package/skills/fui-skill/references/default-function.md +169 -128
  24. package/skills/fui-skill/references/design-modes.md +35 -63
  25. package/skills/fui-skill/references/echart-templates.md +204 -196
  26. package/skills/fui-skill/references/fastproject.md +62 -60
  27. package/skills/fui-skill/references/fsheet.md +109 -124
  28. package/skills/fui-skill/references/fullstack-workflow.md +90 -128
  29. package/skills/fui-skill/references/module-data-patterns.md +31 -40
  30. package/skills/fui-skill/references/module-json-anatomy.md +47 -52
  31. package/skills/fui-skill/references/module-structure.md +80 -196
  32. package/skills/fui-skill/references/new-session.md +49 -51
  33. package/skills/fui-skill/references/pdfmake.md +17 -17
  34. package/skills/fui-skill/references/permission-system.md +89 -108
  35. package/skills/fui-skill/references/platform-architecture.md +128 -153
  36. package/skills/fui-skill/references/project-config.md +102 -134
  37. package/skills/fui-skill/references/project-provisioning.md +139 -244
  38. package/skills/fui-skill/references/script-map.md +208 -242
  39. package/skills/fui-skill/references/sql-clr-functions.md +98 -97
  40. package/skills/fui-skill/references/system-design.md +63 -88
  41. package/skills/fui-skill/references/tapi-file-api.md +46 -52
  42. package/skills/fui-skill/references/tapi-permission-patterns.md +51 -53
  43. package/skills/fui-skill/references/tapi-reference.md +132 -207
  44. package/skills/fui-skill/references/tools-registry.md +84 -460
  45. package/skills/fui-skill/references/ui-crosswindow-patterns.md +79 -75
  46. package/skills/fui-skill/references/ui-dialog-patterns.md +98 -72
  47. package/skills/fui-skill/references/ui-layout-patterns.md +26 -26
  48. package/skills/fui-skill/references/ui-patterns.md +71 -83
  49. package/skills/fui-skill/references/ui-screenshot-review.md +63 -62
  50. package/skills/fui-skill/references/ui-table-cell-patterns.md +61 -59
  51. package/skills/fui-skill/references/ui-templates.md +16 -23
  52. package/skills/fui-skill/references/verification.md +236 -246
  53. package/skills/fui-skill/references/watcher-patterns.md +30 -63
  54. package/skills/fui-skill/references/websocket-realtime.md +83 -66
  55. package/skills/fui-skill/scripts/component-3.0.js +298 -131
  56. package/skills/fui-skill/scripts/component.js +277 -271
  57. package/skills/fui-skill/scripts/componentTable-3.0.js +182 -53
  58. package/skills/fui-skill/scripts/componentTable.js +171 -49
  59. package/skills/fui-skill/scripts/defaultfunction-3.0.js +88 -3
  60. package/skills/fui-skill/scripts/defaultfunction.js +88 -3
  61. package/skills/fui-skill/scripts/fsheet.js +38 -0
  62. package/dist/fui-y8an39cn.js +0 -420
  63. package/skills/fui-skill/README.md +0 -112
  64. package/skills/fui-skill/metadata.json +0 -75
  65. package/skills/fui-skill-cli/SKILL.md +0 -139
  66. package/skills/fui-skill-cli/references/INDEX.md +0 -110
  67. package/skills/fui-skill-cli/references/advanced-techniques.md +0 -168
  68. package/skills/fui-skill-cli/references/coding-standards.md +0 -112
  69. package/skills/fui-skill-cli/references/component-design.md +0 -448
  70. package/skills/fui-skill-cli/references/component-quickref.md +0 -78
  71. package/skills/fui-skill-cli/references/component-table.md +0 -248
  72. package/skills/fui-skill-cli/references/components-dialog.md +0 -191
  73. package/skills/fui-skill-cli/references/components-display.md +0 -141
  74. package/skills/fui-skill-cli/references/components-echart.md +0 -316
  75. package/skills/fui-skill-cli/references/components-input.md +0 -335
  76. package/skills/fui-skill-cli/references/controls-patterns.md +0 -701
  77. package/skills/fui-skill-cli/references/controls-styling-vocabulary.md +0 -137
  78. package/skills/fui-skill-cli/references/db-table-design.md +0 -73
  79. package/skills/fui-skill-cli/references/db-workflow.md +0 -288
  80. package/skills/fui-skill-cli/references/default-function.md +0 -425
  81. package/skills/fui-skill-cli/references/design-modes.md +0 -57
  82. package/skills/fui-skill-cli/references/echart-templates.md +0 -489
  83. package/skills/fui-skill-cli/references/fastproject.md +0 -99
  84. package/skills/fui-skill-cli/references/fsheet.md +0 -203
  85. package/skills/fui-skill-cli/references/fullstack-workflow.md +0 -313
  86. package/skills/fui-skill-cli/references/module-data-patterns.md +0 -117
  87. package/skills/fui-skill-cli/references/module-json-anatomy.md +0 -132
  88. package/skills/fui-skill-cli/references/module-structure.md +0 -141
  89. package/skills/fui-skill-cli/references/new-session.md +0 -85
  90. package/skills/fui-skill-cli/references/pdfmake.md +0 -60
  91. package/skills/fui-skill-cli/references/permission-system.md +0 -150
  92. package/skills/fui-skill-cli/references/platform-architecture.md +0 -269
  93. package/skills/fui-skill-cli/references/project-config.md +0 -303
  94. package/skills/fui-skill-cli/references/project-provisioning.md +0 -278
  95. package/skills/fui-skill-cli/references/script-map.md +0 -262
  96. package/skills/fui-skill-cli/references/sql-clr-functions.md +0 -225
  97. package/skills/fui-skill-cli/references/system-design.md +0 -89
  98. package/skills/fui-skill-cli/references/tapi-file-api.md +0 -185
  99. package/skills/fui-skill-cli/references/tapi-permission-patterns.md +0 -156
  100. package/skills/fui-skill-cli/references/tapi-reference.md +0 -474
  101. package/skills/fui-skill-cli/references/tools-registry.md +0 -84
  102. package/skills/fui-skill-cli/references/ui-crosswindow-patterns.md +0 -321
  103. package/skills/fui-skill-cli/references/ui-dialog-patterns.md +0 -255
  104. package/skills/fui-skill-cli/references/ui-layout-patterns.md +0 -176
  105. package/skills/fui-skill-cli/references/ui-patterns.md +0 -303
  106. package/skills/fui-skill-cli/references/ui-screenshot-review.md +0 -95
  107. package/skills/fui-skill-cli/references/ui-table-cell-patterns.md +0 -318
  108. package/skills/fui-skill-cli/references/ui-templates.md +0 -22
  109. package/skills/fui-skill-cli/references/verification.md +0 -236
  110. package/skills/fui-skill-cli/references/watcher-patterns.md +0 -163
  111. package/skills/fui-skill-cli/references/websocket-realtime.md +0 -271
@@ -1,59 +1,49 @@
1
1
  # tAPI Reference — Transparent Query API (Core)
2
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).
3
+ > Owns: **tAPI conventions: SP naming (`spAPI_*`/`spAPI_AUTH_*`/`spAPIFILE_*`), params, URL, response contract, `/help` route**. Deploy workflow: [db-workflow.md](db-workflow.md).
4
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.
5
+ tAPI auto-generates APIs from SQL SPs; the SP name sets URL, auth type and access (no controllers/routes). Accepts **GET and POST** — don't specify a method in the SP; module.json picks it. Load as needed:
6
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.
7
+ | Topic | File |
8
+ | ------------------------------------------------------------------------------- | ---------------------------------------------------------- |
9
+ | Permission checks in SP body (5 patterns, `@sys_SystemRight`, `getSystemRight`) | [tapi-permission-patterns.md](tapi-permission-patterns.md) |
10
+ | File API — GET/UPLOAD, `spAPIFILE_`, `fileContent`, `tblFileData` | [tapi-file-api.md](tapi-file-api.md) |
8
11
 
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:
12
+ > Image processing, outbound HTTP, email, server file I/O, regex → first check for an existing **SQL CLR function** (`httpCall`, `sendMail`, `ImageResize`, `FileWriter`, `RegexMatch`...): [sql-clr-functions.md](sql-clr-functions.md).
10
13
 
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
14
+ ## 1. SP naming
21
15
 
22
16
  ```
23
17
  spAPI_[AUTH_]FunctionName
24
18
  ```
25
19
 
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:**
20
+ | Part | Required | Meaning |
21
+ | -------------- | -------- | ---------------------------------------------------------------- |
22
+ | `spAPI_` | ✅ | Marks SP as API-callable. Missing → API cannot call it |
23
+ | `AUTH_` | ❌ | No auth required. Omitted → valid token mandatory |
24
+ | `FunctionName` | ✅ | API function name used in URL, e.g. `MessageHome`, `UserSetting` |
33
25
 
34
26
  ```sql
35
27
  spAPI_MessageHome -- có auth
36
28
  spAPI_AUTH_SelectChat -- không cần auth
37
29
  ```
38
30
 
39
- ### File API
31
+ File API:
40
32
 
41
33
  ```
42
34
  spAPIFILE_Document -- SP để GET file
43
35
  spAPIFILE_UPLOAD_Document -- SP để nhận file upload
44
36
  ```
45
37
 
46
- > Chi tiết File API: xem [tapi-file-api.md](tapi-file-api.md).
38
+ Details: [tapi-file-api.md](tapi-file-api.md).
47
39
 
48
40
  ---
49
41
 
50
- ## 2. Tham số SP
51
-
52
- Ba loại tham số:
42
+ ## 2. SP parameters
53
43
 
54
- ### `@url1_`, `@url2_`, `@url3_`... — Route params
44
+ ### `@url1_`, `@url2_`, `@url3_`... — route params
55
45
 
56
- Giá trị lấy trực tiếp từ URL theo thứ tự:
46
+ Taken from URL segments in order:
57
47
 
58
48
  ```
59
49
  https://api.domain.vn/app/FunctionName/value1/value2
@@ -64,22 +54,21 @@ https://api.domain.vn/app/FunctionName/value1/value2
64
54
  @url2_Mode varchar(50), -- nhận "value2"
65
55
  ```
66
56
 
67
- ### `@sys_` — System params (tAPI tự inject, không cần truyền từ client)
57
+ ### `@sys_` — system params (injected by tAPI, never sent by client)
68
58
 
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 |
59
+ | Param | Type | Meaning |
60
+ | -------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------ |
61
+ | `@sys_UserID` | varchar(9) | Authenticated user ID |
62
+ | `@sys_UserName` | varchar(50) | Login name |
63
+ | `@sys_GroupID` | int | User's group |
64
+ | `@sys_DepartmentID` | varchar(9) | Department ID |
65
+ | `@sys_SystemRight` | int | Tiered right (1=low, 9=high) |
66
+ | `@sys_FunctionRight` | varchar(2000) | Function rights as `[AD][RPT][MGR]` |
67
+ | `@sys_SessionID` | varchar(50) | Auth session ID |
68
+ | `@sys_RawData` | nvarchar(max) | Full client JSON body |
69
+ | `@sys_header_{HeaderName}` | varchar(n) | **A family, not one fixed name** — reads any HTTP header; `NULL` if client didn't send it. Naming rule below |
80
70
 
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.
71
+ > **CRITICAL — never put `@sys_` in module.json `IN`.** tAPI fills all `@sys_*` from the session; client values are **ignored or overwritten**.
83
72
  >
84
73
  > ```json
85
74
  > // ❌ SAI — không đưa sys_ vào IN
@@ -88,45 +77,29 @@ https://api.domain.vn/app/FunctionName/value1/value2
88
77
  > // ✅ ĐÚNG — chỉ truyền custom params
89
78
  > "IN": { "ModuleID": "sModuleID" }
90
79
  > ```
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
80
 
94
- #### `@sys_header_*` — đọc HTTP header: thay mỗi dấu `-` bằng `_`
81
+ #### `@sys_header_*` — read HTTP headers: replace each `-` with `_`
95
82
 
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:
83
+ Works for **any** header. `-` is invalid in SQL Server param names, so the literal name won't compile:
100
84
 
101
85
  ```sql
102
86
  -- ❌ SAI — lỗi cú pháp ngay lúc CREATE PROCEDURE, không phải lỗi runtime
103
87
  @sys_header_User-Agent varchar(500)
104
88
  ```
105
89
 
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**.
90
+ - **No quoting works**: `@[sys_header_User-Agent]` and `@"sys_header_User-Agent"` are both invalid. Renaming is the **only** way.
91
+ - **Each `-` → one `_`.** 1-to-1: don't drop characters, change case, or merge segments.
109
92
 
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.
93
+ | HTTP header | SP param |
94
+ | ------------------ | ---------------------------------------- |
95
+ | `User-Agent` | `@sys_header_User_Agent` |
96
+ | `Sec-Ch-Ua-Mobile` | `@sys_header_Sec_Ch_Ua_Mobile` |
97
+ | `X-Forwarded-For` | `@sys_header_X_Forwarded_For` |
98
+ | `Accept-Language` | `@sys_header_Accept_Language` |
99
+ | `Cookie` | `@sys_header_Cookie` — no `-`, unchanged |
113
100
 
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 `-`.
101
+ - Reverse (reading an existing SP): drop `@sys_header_`, turn remaining `_` into `-` — `@sys_header_Accept_Language` → `Accept-Language`.
102
+ - Matching is **case-insensitive** (prefix and name): `@sys_header_User_Agent`, `@sys_Header_User_Agent`, `@sys_header_USER_AGENT` are the same header (so `@sys_Header_RequestID` works). Still use standard casing (`User-Agent`, not `USER_AGENT`). Only `_` for `-` is mandatory.
130
103
 
131
104
  ```sql
132
105
  CREATE PROCEDURE spAPI_AuditLog_Insert
@@ -145,27 +118,15 @@ BEGIN
145
118
  END
146
119
  ```
147
120
 
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.
121
+ - **Still `@sys_*`:** auto-filled, **not** in `IN`, **no default** (`= null` is a hard error; `fui sp deploy` blocks it, see below).
122
+ - **Missing header → `NULL`** (not `''`) — normal, e.g. script/server-to-server calls often lack `User-Agent`. Wrap in `ISNULL(...)` at use: comparisons give UNKNOWN (`WHERE @sys_header_User_Agent <> 'bot'` **drops** the row), and `+` concatenation gives `NULL` (blanks the whole log string).
123
+ - **Untrusted (client-supplied, spoofable).** Log/stats only, never authorization; take rights from `@sys_UserID`/`@sys_SystemRight` (from the verified token).
162
124
 
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).
125
+ Check the name tAPI sees: `fui sp help <name>` (§3.1).
165
126
 
166
- ### Custom params — Tham số nghiệp vụ do lập trình viên định nghĩa
127
+ ### Custom params — business params
167
128
 
168
- Tên tham số = tên field trong JSON body gửi lên:
129
+ Param name = JSON body field name:
169
130
 
170
131
  ```sql
171
132
  @StudentID varchar(9),
@@ -173,36 +134,14 @@ Tên tham số = tên field trong JSON body gửi lên:
173
134
  @Keyword nvarchar(200)
174
135
  ```
175
136
 
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.
137
+ #### Param binding is LENIENT — never errors
188
138
 
189
- Hai hệ quả phải nhớ:
139
+ - Body **lacks** a key → param gets **`NULL`**. Body has an **extra** key → **silently ignored**.
140
+ - A typo in `IN` (`StudentId` vs `StudentID`) → SP gets `NULL`, empty/wrong data, HTTP 200. Most common silent wiring bug.
141
+ - **Renaming/removing an SP param is a contract change with NO signal**: old modules keep sending old keys. Audit callers or compare with `fui sp help <name>` (§3.1).
142
+ - **`NULL` follows T-SQL rules** (`<>` → UNKNOWN drops rows; `+` → `NULL`): wrap possibly-absent params in `ISNULL(...)` at use.
190
143
 
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)`.
144
+ > **CRITICAL — no default value on any param** (`= value` or `= null`, including `@sys_*`). tAPI **always passes every param** (`NULL` if not sent), so `@HeID int = 99` silently gives `NULL`, not `99`. `fui sp deploy` hard-blocks it for `spAPI_*`/`spAPIFILE_*` (`detectDefaultApiParams()` in `src/tools/db.ts`); `fui sp verify` warns earlier. Set defaults **in the body**: `SET @HeID = ISNULL(@HeID, 99)`.
206
145
  >
207
146
  > ```sql
208
147
  > -- ❌ SAI
@@ -220,30 +159,26 @@ Kiểm tên khoá thật sự tới được SP: `db_sp_help({ name })` in danh
220
159
  > @sys_SystemRight int
221
160
  > ```
222
161
 
223
- Client gửi: `POST /app/FunctionName` với body `{ "StudentID": "SV001", "SemesterID": 1 }`
162
+ Client sends: `POST /app/FunctionName` with body `{ "StudentID": "SV001", "SemesterID": 1 }`
224
163
 
225
164
  ---
226
165
 
227
- ## 3. URL gọi API
166
+ ## 3. API URL
228
167
 
229
168
  ```
230
169
  {domain}/{apiName}/{FunctionName}/{url1}/{url2}...
231
170
  {domain}/{apiName}/auth/{FunctionName}/{url1}/{url2}...
232
171
  ```
233
172
 
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).
173
+ | Part | Meaning |
174
+ | ----------------- | --------------------------------------------------------------------------------------------------------------- |
175
+ | `domain` | API domain, e.g. `https://api.example.vn` |
176
+ | `apiName` | App apiName: `me`, `congvan`, `calen`... — also the **DB connection alias** in the workspace (`_db/{apiName}/`) |
177
+ | `auth` | Add when SP has `AUTH_` (no token) |
178
+ | `FunctionName` | SP name after the prefix |
179
+ | `url1`, `url2`... | Route params for `@url1_`, `@url2_` |
245
180
 
246
- **Ví dụ:**
181
+ **A project can have MANY `apiName`s** (one DB alias each). `"API": "/{alias}/Ten"` in module.json names the SP's database; fui uses that alias for `_db/{alias}/` and as `--db` of DB commands ([db-workflow.md](db-workflow.md) §1, §4).
247
182
 
248
183
  ```
249
184
  SP: spAPI_AUTH_Select_Chat
@@ -256,8 +191,7 @@ SP: spAPI_GetStudentDetail (@url1_StudentID)
256
191
  URL: https://api.example.vn/me/GetStudentDetail/SV001
257
192
  ```
258
193
 
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.
194
+ > **CRITICAL — common bug: missing `/` between `domain` and `apiName`.** `domain` NEVER ends with `/`; insert exactly ONE `/` before `apiName`.
261
195
  >
262
196
  > ```
263
197
  > domain=tapi.lhu.edu.vn, apiName=ts, function=TS_Report_HeXetTuyenSelectAll
@@ -266,32 +200,30 @@ URL: https://api.example.vn/me/GetStudentDetail/SV001
266
200
  > ✅ ĐÚNG: https://tapi.lhu.edu.vn/ts/TS_Report_HeXetTuyenSelectAll
267
201
  > ```
268
202
  >
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.
203
+ > **Don't build URLs by hand/memory** — copy the `Wiring URL: ...` line from `fui sp verify` (or `fui sp help`) verbatim.
270
204
 
271
- ### 3.1 Cache tham số & route `/help`
205
+ ### 3.1 Param cache & `/help` route
272
206
 
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`):
207
+ tAPI **caches each function's param signature** after the first call. After ALTERing a `spAPI_*`/`spAPIFILE_*` SP's params (add/remove/rename/retype), the live endpoint keeps the **old** signature until cleared. Route = API URL with `/help` right after domain:
276
208
 
277
209
  ```
278
210
  {domain}/help/{apiName}/{FunctionName} ← xoá cache + trả về tham số của hàm
279
211
  {domain}/help/{apiName}/auth/{FunctionName}
280
212
  ```
281
213
 
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.
214
+ It **(a) clears the cache** and **(b) returns the input params** (useful before wiring `IN`).
215
+
216
+ **Mandatory after every SP ALTER/UPDATE** (else old signature → param errors even with a correct SP):
283
217
 
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ũ.
218
+ - `fui sp deploy` auto-calls `/help` **once, standard URL; ignore failure** (SP is already deployed).
219
+ - `ALTER PROCEDURE` via `fui exec`: **no auto-clear → run `fui sp help` manually** right after. The only manual case.
220
+ - **`/help` errors → STOP, no URL variants.** `/auth/` or not follows from the SP name; retrying changes nothing. Re-run `fui sp help` only if the live endpoint truly still takes old params.
289
221
 
290
222
  ---
291
223
 
292
- ## 4. Format dữ liệu trả về
224
+ ## 4. Response format
293
225
 
294
- ### 4.1 Một SELECT — trả về array
226
+ ### 4.1 One SELECT → array
295
227
 
296
228
  ```sql
297
229
  SELECT StudentID, FullName, GPA FROM tblStudent WHERE ClassID = @ClassID
@@ -301,14 +233,14 @@ SELECT StudentID, FullName, GPA FROM tblStudent WHERE ClassID = @ClassID
301
233
  {
302
234
  "data": [
303
235
  { "StudentID": "SV001", "FullName": "Nguyen Van A", "GPA": 3.5 },
304
- { "StudentID": "SV002", "FullName": "Tran Thi B", "GPA": 3.2 }
236
+ { "StudentID": "SV002", "FullName": "Tran Thi B", "GPA": 3.2 }
305
237
  ]
306
238
  }
307
239
  ```
308
240
 
309
- ### 4.2 Nhiều SELECT — trả về array of arrays
241
+ ### 4.2 Multiple SELECTs → array of arrays
310
242
 
311
- Mỗi `SELECT` trong SP tạo thành một mảng con trong `data`:
243
+ Each `SELECT` becomes one sub-array in `data`:
312
244
 
313
245
  ```sql
314
246
  SELECT TotalCount = COUNT(*) FROM tblStudent WHERE ClassID = @ClassID
@@ -327,23 +259,23 @@ SELECT StudentID, FullName FROM tblStudent WHERE ClassID = @ClassID
327
259
  }
328
260
  ```
329
261
 
330
- **Trong FUI module.json**, đọc từng mảng con qua index:
262
+ In module.json, read sub-arrays by index:
263
+
331
264
  ```json
332
265
  "OUT": "studentList",
333
266
  "CALLBACK": { "EXE": "vueData.total = vueData.studentList[0][0].TotalCount; vueData.students = vueData.studentList[1]" }
334
267
  ```
335
268
 
336
- ### 4.3 `convert_to_object` — convert một dòng thành object
269
+ ### 4.3 `convert_to_object` — row → object
337
270
 
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.
271
+ Returns an object instead of an array. **Many SELECTs** may use it; all merge into one response object.
339
272
 
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).
273
+ > ⚠️ When a SELECT has `convert_to_object` **and** a `json_data` column with a JSON string, it must return **exactly 1 row**. More than 1 → tAPI returns `{}`.
341
274
 
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
275
+ - `convert_to_object = 'key'` → nested object under that key
276
+ - `convert_to_object = ''` → merged into root
345
277
 
346
- **Ví dụ 1 — Kết hợp named key và root merge:**
278
+ **Ex 1 — named key + root merge:**
347
279
 
348
280
  ```sql
349
281
  SELECT convert_to_object = 'sinhvien',
@@ -365,7 +297,7 @@ SELECT convert_to_object = '',
365
297
  }
366
298
  ```
367
299
 
368
- **Ví dụ 2 — `json_data` override field cùng tên:**
300
+ **Ex 2 — `json_data` overrides same-name fields** (parsed and merged after SQL columns, so `json_data` wins, e.g. `ProjectName`):
369
301
 
370
302
  ```sql
371
303
  SELECT convert_to_object = '',
@@ -385,9 +317,7 @@ SELECT convert_to_object = '',
385
317
  }
386
318
  ```
387
319
 
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:**
320
+ **Ex 3 — multiple `convert_to_object = ''` SELECTs all merge into root:**
391
321
 
392
322
  ```sql
393
323
  SELECT convert_to_object = '',
@@ -412,18 +342,16 @@ SELECT convert_to_object = '',
412
342
  }
413
343
  ```
414
344
 
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
345
+ ### 4.4 `[json_data:FieldName]` — JSON string column → object
418
346
 
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]`:
347
+ Return a JSON-string column as an object:
420
348
 
421
349
  ```sql
422
350
  SELECT PartID, PartName, [json_data:FileInfo] = FileInfo
423
351
  FROM tblPart WHERE PartID = @url1_PartID
424
352
  ```
425
353
 
426
- tAPI tự động parse cột có alias `json_data:FieldName` thành nested JSON object trong response:
354
+ tAPI parses it into a nested object:
427
355
 
428
356
  ```json
429
357
  {
@@ -433,17 +361,15 @@ tAPI tự động parse cột có alias `json_data:FieldName` thành nested JSON
433
361
  }
434
362
  ```
435
363
 
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.
364
+ DB column stays a string. Name after `:` = response field; after `=` = real column.
439
365
 
440
366
  ---
441
367
 
442
- ## 5. Xử lý lỗi trong SP
368
+ ## 5. Error handling in SP
443
369
 
444
- Tất cả lỗi dùng `RAISERROR` với severity **16** — không dùng severity khác.
370
+ All errors use `RAISERROR` with severity **16**.
445
371
 
446
- ### Lỗi thông thường (validation, ràng buộc dữ liệu)
372
+ ### Normal errors (validation, data constraints)
447
373
 
448
374
  ```sql
449
375
  RAISERROR(N'Dữ liệu không hợp lệ', 16, 1)
@@ -454,7 +380,7 @@ RETURN
454
380
  { "Message": "Dữ liệu không hợp lệ" }
455
381
  ```
456
382
 
457
- ### Lỗi Unauthorized (thiếu quyền) — prefix `[Unauthorized]` → HTTP 401
383
+ ### Unauthorized — prefix `[Unauthorized]` → HTTP 401
458
384
 
459
385
  ```sql
460
386
  IF @sys_SystemRight < 2
@@ -468,34 +394,33 @@ END
468
394
  { "Message": "Bạn không có quyền trên chức năng này." }
469
395
  ```
470
396
 
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.
397
+ **Mandatory:**
476
398
 
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).
399
+ - Always severity `16` — never `11`, `14` or anything else.
400
+ - Always `RETURN` right after `RAISERROR`.
401
+ - `N` prefix required for Vietnamese Unicode.
402
+ - **Every permission error (missing `SystemRight`/`FunctionRight`) must start with `[Unauthorized]`** → HTTP `401` instead of `200`/`500`; client `errorMess()` (`fastproject.js`) redirects to login if message contains "token", else shows a permission error. tAPI strips the prefix from `Message`. Validation errors **don't** use it.
403
+
404
+ > Detailed permission checks (5 patterns, `getSystemRight`, check order, message conventions): [tapi-permission-patterns.md](tapi-permission-patterns.md).
478
405
 
479
406
  ---
480
407
 
481
- ## 6. Quy tắc thiết kế SP tốt
408
+ ## 6. SP design rules
482
409
 
483
- ### Đặt tên SP
410
+ ### Naming
484
411
 
485
412
  ```
486
413
  spAPI_{Entity}{Action}
487
414
  ```
488
415
 
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:**
416
+ | Action | Meaning |
417
+ | ------------------- | ----------------------- |
418
+ | `Select` / `List` | List query |
419
+ | `Get` | One record's detail |
420
+ | `Insert` / `Add` | Create |
421
+ | `Update` / `Edit` | Update |
422
+ | `Delete` / `Remove` | Delete |
423
+ | `AUTH_Select` | Public query (no token) |
499
424
 
500
425
  ```sql
501
426
  spAPI_StudentList -- GET danh sách sinh viên
@@ -506,7 +431,7 @@ spAPI_StudentDelete -- POST xoá (url1 = StudentID)
506
431
  spAPI_AUTH_CourseList -- Danh sách môn học công khai
507
432
  ```
508
433
 
509
- ### Wiring trong FUI module.json
434
+ ### Wiring in FUI module.json
510
435
 
511
436
  ```json
512
437
  "fetchStudents": {
@@ -532,18 +457,18 @@ spAPI_AUTH_CourseList -- Danh sách môn học công khai
532
457
 
533
458
  ---
534
459
 
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.
460
+ ## 7. SP design checklist
461
+
462
+ 1. `spAPI_` prefix mandatory — missing means API can't call it.
463
+ 2. Add `AUTH_` for public endpoints (login page, lookup data).
464
+ 3. Always include `@sys_UserID` in SPs that write data (audit trail).
465
+ 4. No `SELECT *` — list columns.
466
+ 5. Check permissions at SP start if needed, with `RAISERROR` + `RETURN` — see [tapi-permission-patterns.md](tapi-permission-patterns.md).
467
+ 6. Multiple SELECTs → client reads by index `data[0]`, `data[1]`.
468
+ 7. Single record → use `convert_to_object` instead of `data[0][0]`.
469
+ 8. **No `GRANT EXECUTE` in the .sql file** — the SP body runs to end of batch and tAPI has no `GO` support, so a GRANT after `END` is **swallowed into the SP body** (runs every call, grants nothing, no error). `fui sp deploy` grants and confirms via `sys.database_permissions` — read its `✓`/`❗` line ([verification.md](verification.md#security)).
470
+ 9. **Never put `sys_*` params in module.json `IN`** — tAPI injects them.
471
+ 9b. **Headers** → `@sys_header_{HeaderName}`, **each `-` → `_`**; `-` is a syntax error, brackets don't help (§2). Log only, never gate permissions.
472
+ 9c. **Binding never errors** (missing → `NULL`, extra → ignored): check `IN` names with `fui sp help`. `DEFAULT` is useless; use `SET @x = ISNULL(@x, <giá trị>)`.
473
+ 10. **File GET/UPLOAD**: prefix `spAPIFILE_`, binary column alias `fileContent`, params `@sys_FileContent`/`@sys_FileName`, standard table `tblFileData` — see [tapi-file-api.md](tapi-file-api.md). Don't infer from training data.
474
+ 11. **Any ALTER/UPDATE of a deployed SP** (params, logic, anything) → **MUST clear tAPI cache** via `fui sp help` (§3.1). `fui sp deploy` does it (once; ignore failure); only ALTER via `fui exec` needs it manually.