@fui-org/fui-cli 2.1.4 → 2.2.1

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 (214) hide show
  1. package/README.md +16 -7
  2. package/dist/fui-xg17j3f9.js +396 -0
  3. package/dist/fui.js +1 -1
  4. package/package.json +1 -1
  5. package/skills/fui/SKILL.md +152 -39
  6. package/skills/{fui-skill → fui}/assets/projectdefaultstyle.css +489 -489
  7. package/skills/{fui-skill → fui}/examples/component.vue +162 -162
  8. package/skills/{fui-skill → fui}/references/INDEX.md +4 -5
  9. package/skills/{fui-skill → fui}/references/advanced-techniques.md +170 -168
  10. package/skills/{fui-skill → fui}/references/component-design.md +2 -2
  11. package/skills/{fui-skill → fui}/references/component-quickref.md +78 -78
  12. package/skills/{fui-skill → fui}/references/component-table.md +287 -287
  13. package/skills/{fui-skill → fui}/references/components-dialog.md +191 -191
  14. package/skills/{fui-skill → fui}/references/components-display.md +141 -141
  15. package/skills/{fui-skill → fui}/references/components-echart.md +316 -316
  16. package/skills/{fui-skill → fui}/references/components-input.md +335 -335
  17. package/skills/{fui-skill → fui}/references/controls-patterns.md +2 -2
  18. package/skills/{fui-skill → fui}/references/controls-styling-vocabulary.md +173 -173
  19. package/skills/{fui-skill → fui}/references/db-workflow.md +9 -9
  20. package/skills/{fui-skill → fui}/references/default-function.md +456 -456
  21. package/skills/{fui-skill → fui}/references/design-modes.md +57 -57
  22. package/skills/{fui-skill → fui}/references/echart-templates.md +489 -489
  23. package/skills/{fui-skill → fui}/references/fastproject.md +99 -99
  24. package/skills/{fui-skill → fui}/references/fullstack-workflow.md +314 -313
  25. package/skills/{fui-skill → fui}/references/module-data-patterns.md +117 -117
  26. package/skills/{fui-skill → fui}/references/module-json-anatomy.md +132 -132
  27. package/skills/{fui-skill → fui}/references/module-structure.md +144 -144
  28. package/skills/{fui-skill → fui}/references/new-session.md +3 -20
  29. package/skills/{fui-skill → fui}/references/pdfmake.md +60 -60
  30. package/skills/{fui-skill → fui}/references/permission-system.md +1 -1
  31. package/skills/{fui-skill → fui}/references/platform-architecture.md +269 -269
  32. package/skills/{fui-skill → fui}/references/project-config.md +303 -303
  33. package/skills/{fui-skill → fui}/references/project-provisioning.md +4 -4
  34. package/skills/{fui-skill → fui}/references/script-map.md +262 -262
  35. package/skills/{fui-skill → fui}/references/sql-clr-functions.md +225 -225
  36. package/skills/{fui-skill → fui}/references/system-design.md +3 -3
  37. package/skills/{fui-skill → fui}/references/tapi-permission-patterns.md +156 -156
  38. package/skills/{fui-skill → fui}/references/tapi-reference.md +474 -474
  39. package/skills/fui/references/tools-registry.md +214 -0
  40. package/skills/{fui-skill → fui}/references/ui-crosswindow-patterns.md +321 -321
  41. package/skills/{fui-skill → fui}/references/ui-dialog-patterns.md +255 -255
  42. package/skills/{fui-skill → fui}/references/ui-layout-patterns.md +176 -176
  43. package/skills/{fui-skill → fui}/references/ui-patterns.md +303 -303
  44. package/skills/{fui-skill → fui}/references/ui-screenshot-review.md +95 -95
  45. package/skills/{fui-skill → fui}/references/ui-table-cell-patterns.md +318 -318
  46. package/skills/{fui-skill → fui}/references/verification.md +18 -18
  47. package/skills/{fui-skill → fui}/references/watcher-patterns.md +163 -163
  48. package/skills/{fui-skill → fui}/references/websocket-realtime.md +271 -271
  49. package/skills/{fui-skill → fui}/scripts/component-3.0.js +2715 -2715
  50. package/skills/{fui-skill → fui}/scripts/component.js +3147 -3147
  51. package/skills/{fui-skill → fui}/scripts/componentTable-3.0.js +1038 -1038
  52. package/skills/{fui-skill → fui}/scripts/componentTable.js +891 -891
  53. package/skills/{fui-skill → fui}/scripts/defaultfunction-3.0.js +866 -866
  54. package/skills/{fui-skill → fui}/scripts/defaultfunction.js +1051 -1051
  55. package/skills/{fui-skill → fui}/scripts/fastproject-3.0.js +870 -870
  56. package/skills/{fui-skill → fui}/scripts/fastproject.js +828 -828
  57. package/dist/fui-h4z7zhk2.js +0 -381
  58. package/skills/fui-skill/SKILL.md +0 -139
  59. package/skills/fui-skill/references/tools-registry.md +0 -84
  60. /package/skills/{fui-skill → fui}/assets/projectdefaultstyle-3.0.css +0 -0
  61. /package/skills/{fui-skill → fui}/design-md/airbnb/DESIGN.md +0 -0
  62. /package/skills/{fui-skill → fui}/design-md/airbnb/README.md +0 -0
  63. /package/skills/{fui-skill → fui}/design-md/airtable/DESIGN.md +0 -0
  64. /package/skills/{fui-skill → fui}/design-md/airtable/README.md +0 -0
  65. /package/skills/{fui-skill → fui}/design-md/apple/DESIGN.md +0 -0
  66. /package/skills/{fui-skill → fui}/design-md/apple/README.md +0 -0
  67. /package/skills/{fui-skill → fui}/design-md/asu/DESIGN.md +0 -0
  68. /package/skills/{fui-skill → fui}/design-md/asu/README.md +0 -0
  69. /package/skills/{fui-skill → fui}/design-md/binance/DESIGN.md +0 -0
  70. /package/skills/{fui-skill → fui}/design-md/binance/README.md +0 -0
  71. /package/skills/{fui-skill → fui}/design-md/bmw/DESIGN.md +0 -0
  72. /package/skills/{fui-skill → fui}/design-md/bmw/README.md +0 -0
  73. /package/skills/{fui-skill → fui}/design-md/bmw-m/DESIGN.md +0 -0
  74. /package/skills/{fui-skill → fui}/design-md/bmw-m/README.md +0 -0
  75. /package/skills/{fui-skill → fui}/design-md/bugatti/DESIGN.md +0 -0
  76. /package/skills/{fui-skill → fui}/design-md/bugatti/README.md +0 -0
  77. /package/skills/{fui-skill → fui}/design-md/cal/DESIGN.md +0 -0
  78. /package/skills/{fui-skill → fui}/design-md/cal/README.md +0 -0
  79. /package/skills/{fui-skill → fui}/design-md/claude/DESIGN.md +0 -0
  80. /package/skills/{fui-skill → fui}/design-md/claude/README.md +0 -0
  81. /package/skills/{fui-skill → fui}/design-md/clay/DESIGN.md +0 -0
  82. /package/skills/{fui-skill → fui}/design-md/clay/README.md +0 -0
  83. /package/skills/{fui-skill → fui}/design-md/clickhouse/DESIGN.md +0 -0
  84. /package/skills/{fui-skill → fui}/design-md/clickhouse/README.md +0 -0
  85. /package/skills/{fui-skill → fui}/design-md/cohere/DESIGN.md +0 -0
  86. /package/skills/{fui-skill → fui}/design-md/cohere/README.md +0 -0
  87. /package/skills/{fui-skill → fui}/design-md/coinbase/DESIGN.md +0 -0
  88. /package/skills/{fui-skill → fui}/design-md/coinbase/README.md +0 -0
  89. /package/skills/{fui-skill → fui}/design-md/composio/DESIGN.md +0 -0
  90. /package/skills/{fui-skill → fui}/design-md/composio/README.md +0 -0
  91. /package/skills/{fui-skill → fui}/design-md/cursor/DESIGN.md +0 -0
  92. /package/skills/{fui-skill → fui}/design-md/cursor/README.md +0 -0
  93. /package/skills/{fui-skill → fui}/design-md/elevenlabs/DESIGN.md +0 -0
  94. /package/skills/{fui-skill → fui}/design-md/elevenlabs/README.md +0 -0
  95. /package/skills/{fui-skill → fui}/design-md/expo/DESIGN.md +0 -0
  96. /package/skills/{fui-skill → fui}/design-md/expo/README.md +0 -0
  97. /package/skills/{fui-skill → fui}/design-md/ferrari/DESIGN.md +0 -0
  98. /package/skills/{fui-skill → fui}/design-md/ferrari/README.md +0 -0
  99. /package/skills/{fui-skill → fui}/design-md/figma/DESIGN.md +0 -0
  100. /package/skills/{fui-skill → fui}/design-md/figma/README.md +0 -0
  101. /package/skills/{fui-skill → fui}/design-md/framer/DESIGN.md +0 -0
  102. /package/skills/{fui-skill → fui}/design-md/framer/README.md +0 -0
  103. /package/skills/{fui-skill → fui}/design-md/fui/DESIGN.md +0 -0
  104. /package/skills/{fui-skill → fui}/design-md/hashicorp/DESIGN.md +0 -0
  105. /package/skills/{fui-skill → fui}/design-md/hashicorp/README.md +0 -0
  106. /package/skills/{fui-skill → fui}/design-md/ibm/DESIGN.md +0 -0
  107. /package/skills/{fui-skill → fui}/design-md/ibm/README.md +0 -0
  108. /package/skills/{fui-skill → fui}/design-md/intercom/DESIGN.md +0 -0
  109. /package/skills/{fui-skill → fui}/design-md/intercom/README.md +0 -0
  110. /package/skills/{fui-skill → fui}/design-md/kraken/DESIGN.md +0 -0
  111. /package/skills/{fui-skill → fui}/design-md/kraken/README.md +0 -0
  112. /package/skills/{fui-skill → fui}/design-md/lamborghini/DESIGN.md +0 -0
  113. /package/skills/{fui-skill → fui}/design-md/lamborghini/README.md +0 -0
  114. /package/skills/{fui-skill → fui}/design-md/linear.app/DESIGN.md +0 -0
  115. /package/skills/{fui-skill → fui}/design-md/linear.app/README.md +0 -0
  116. /package/skills/{fui-skill → fui}/design-md/lovable/DESIGN.md +0 -0
  117. /package/skills/{fui-skill → fui}/design-md/lovable/README.md +0 -0
  118. /package/skills/{fui-skill → fui}/design-md/mastercard/DESIGN.md +0 -0
  119. /package/skills/{fui-skill → fui}/design-md/mastercard/README.md +0 -0
  120. /package/skills/{fui-skill → fui}/design-md/meta/DESIGN.md +0 -0
  121. /package/skills/{fui-skill → fui}/design-md/meta/README.md +0 -0
  122. /package/skills/{fui-skill → fui}/design-md/minimax/DESIGN.md +0 -0
  123. /package/skills/{fui-skill → fui}/design-md/minimax/README.md +0 -0
  124. /package/skills/{fui-skill → fui}/design-md/mintlify/DESIGN.md +0 -0
  125. /package/skills/{fui-skill → fui}/design-md/mintlify/README.md +0 -0
  126. /package/skills/{fui-skill → fui}/design-md/miro/DESIGN.md +0 -0
  127. /package/skills/{fui-skill → fui}/design-md/miro/README.md +0 -0
  128. /package/skills/{fui-skill → fui}/design-md/mistral.ai/DESIGN.md +0 -0
  129. /package/skills/{fui-skill → fui}/design-md/mistral.ai/README.md +0 -0
  130. /package/skills/{fui-skill → fui}/design-md/mongodb/DESIGN.md +0 -0
  131. /package/skills/{fui-skill → fui}/design-md/mongodb/README.md +0 -0
  132. /package/skills/{fui-skill → fui}/design-md/nike/DESIGN.md +0 -0
  133. /package/skills/{fui-skill → fui}/design-md/nike/README.md +0 -0
  134. /package/skills/{fui-skill → fui}/design-md/notion/DESIGN.md +0 -0
  135. /package/skills/{fui-skill → fui}/design-md/notion/README.md +0 -0
  136. /package/skills/{fui-skill → fui}/design-md/nvidia/DESIGN.md +0 -0
  137. /package/skills/{fui-skill → fui}/design-md/nvidia/README.md +0 -0
  138. /package/skills/{fui-skill → fui}/design-md/ollama/DESIGN.md +0 -0
  139. /package/skills/{fui-skill → fui}/design-md/ollama/README.md +0 -0
  140. /package/skills/{fui-skill → fui}/design-md/opencode.ai/DESIGN.md +0 -0
  141. /package/skills/{fui-skill → fui}/design-md/opencode.ai/README.md +0 -0
  142. /package/skills/{fui-skill → fui}/design-md/pinterest/DESIGN.md +0 -0
  143. /package/skills/{fui-skill → fui}/design-md/pinterest/README.md +0 -0
  144. /package/skills/{fui-skill → fui}/design-md/playstation/DESIGN.md +0 -0
  145. /package/skills/{fui-skill → fui}/design-md/playstation/README.md +0 -0
  146. /package/skills/{fui-skill → fui}/design-md/posthog/DESIGN.md +0 -0
  147. /package/skills/{fui-skill → fui}/design-md/posthog/README.md +0 -0
  148. /package/skills/{fui-skill → fui}/design-md/raycast/DESIGN.md +0 -0
  149. /package/skills/{fui-skill → fui}/design-md/raycast/README.md +0 -0
  150. /package/skills/{fui-skill → fui}/design-md/renault/DESIGN.md +0 -0
  151. /package/skills/{fui-skill → fui}/design-md/renault/README.md +0 -0
  152. /package/skills/{fui-skill → fui}/design-md/replicate/DESIGN.md +0 -0
  153. /package/skills/{fui-skill → fui}/design-md/replicate/README.md +0 -0
  154. /package/skills/{fui-skill → fui}/design-md/resend/DESIGN.md +0 -0
  155. /package/skills/{fui-skill → fui}/design-md/resend/README.md +0 -0
  156. /package/skills/{fui-skill → fui}/design-md/revolut/DESIGN.md +0 -0
  157. /package/skills/{fui-skill → fui}/design-md/revolut/README.md +0 -0
  158. /package/skills/{fui-skill → fui}/design-md/runwayml/DESIGN.md +0 -0
  159. /package/skills/{fui-skill → fui}/design-md/runwayml/README.md +0 -0
  160. /package/skills/{fui-skill → fui}/design-md/sanity/DESIGN.md +0 -0
  161. /package/skills/{fui-skill → fui}/design-md/sanity/README.md +0 -0
  162. /package/skills/{fui-skill → fui}/design-md/sentry/DESIGN.md +0 -0
  163. /package/skills/{fui-skill → fui}/design-md/sentry/README.md +0 -0
  164. /package/skills/{fui-skill → fui}/design-md/shopify/DESIGN.md +0 -0
  165. /package/skills/{fui-skill → fui}/design-md/shopify/README.md +0 -0
  166. /package/skills/{fui-skill → fui}/design-md/slack/DESIGN.md +0 -0
  167. /package/skills/{fui-skill → fui}/design-md/spacex/DESIGN.md +0 -0
  168. /package/skills/{fui-skill → fui}/design-md/spacex/README.md +0 -0
  169. /package/skills/{fui-skill → fui}/design-md/spotify/DESIGN.md +0 -0
  170. /package/skills/{fui-skill → fui}/design-md/spotify/README.md +0 -0
  171. /package/skills/{fui-skill → fui}/design-md/starbucks/DESIGN.md +0 -0
  172. /package/skills/{fui-skill → fui}/design-md/starbucks/README.md +0 -0
  173. /package/skills/{fui-skill → fui}/design-md/stripe/DESIGN.md +0 -0
  174. /package/skills/{fui-skill → fui}/design-md/stripe/README.md +0 -0
  175. /package/skills/{fui-skill → fui}/design-md/supabase/DESIGN.md +0 -0
  176. /package/skills/{fui-skill → fui}/design-md/supabase/README.md +0 -0
  177. /package/skills/{fui-skill → fui}/design-md/superhuman/DESIGN.md +0 -0
  178. /package/skills/{fui-skill → fui}/design-md/superhuman/README.md +0 -0
  179. /package/skills/{fui-skill → fui}/design-md/tesla/DESIGN.md +0 -0
  180. /package/skills/{fui-skill → fui}/design-md/tesla/README.md +0 -0
  181. /package/skills/{fui-skill → fui}/design-md/theverge/DESIGN.md +0 -0
  182. /package/skills/{fui-skill → fui}/design-md/theverge/README.md +0 -0
  183. /package/skills/{fui-skill → fui}/design-md/together.ai/DESIGN.md +0 -0
  184. /package/skills/{fui-skill → fui}/design-md/together.ai/README.md +0 -0
  185. /package/skills/{fui-skill → fui}/design-md/uber/DESIGN.md +0 -0
  186. /package/skills/{fui-skill → fui}/design-md/uber/README.md +0 -0
  187. /package/skills/{fui-skill → fui}/design-md/vercel/DESIGN.md +0 -0
  188. /package/skills/{fui-skill → fui}/design-md/vercel/README.md +0 -0
  189. /package/skills/{fui-skill → fui}/design-md/vodafone/DESIGN.md +0 -0
  190. /package/skills/{fui-skill → fui}/design-md/vodafone/README.md +0 -0
  191. /package/skills/{fui-skill → fui}/design-md/voltagent/DESIGN.md +0 -0
  192. /package/skills/{fui-skill → fui}/design-md/voltagent/README.md +0 -0
  193. /package/skills/{fui-skill → fui}/design-md/warp/DESIGN.md +0 -0
  194. /package/skills/{fui-skill → fui}/design-md/warp/README.md +0 -0
  195. /package/skills/{fui-skill → fui}/design-md/webflow/DESIGN.md +0 -0
  196. /package/skills/{fui-skill → fui}/design-md/webflow/README.md +0 -0
  197. /package/skills/{fui-skill → fui}/design-md/wired/DESIGN.md +0 -0
  198. /package/skills/{fui-skill → fui}/design-md/wired/README.md +0 -0
  199. /package/skills/{fui-skill → fui}/design-md/wise/DESIGN.md +0 -0
  200. /package/skills/{fui-skill → fui}/design-md/wise/README.md +0 -0
  201. /package/skills/{fui-skill → fui}/design-md/x.ai/DESIGN.md +0 -0
  202. /package/skills/{fui-skill → fui}/design-md/x.ai/README.md +0 -0
  203. /package/skills/{fui-skill → fui}/design-md/zapier/DESIGN.md +0 -0
  204. /package/skills/{fui-skill → fui}/design-md/zapier/README.md +0 -0
  205. /package/skills/{fui-skill → fui}/examples/f-table-patterns.json +0 -0
  206. /package/skills/{fui-skill → fui}/examples/module-patterns.json +0 -0
  207. /package/skills/{fui-skill → fui}/examples/project-patterns.json +0 -0
  208. /package/skills/{fui-skill → fui}/references/coding-standards.md +0 -0
  209. /package/skills/{fui-skill → fui}/references/db-table-design.md +0 -0
  210. /package/skills/{fui-skill → fui}/references/fsheet.md +0 -0
  211. /package/skills/{fui-skill → fui}/references/tapi-file-api.md +0 -0
  212. /package/skills/{fui-skill → fui}/references/ui-templates.md +0 -0
  213. /package/skills/{fui-skill → fui}/scripts/fechart.js +0 -0
  214. /package/skills/{fui-skill → fui}/scripts/fsheet.js +0 -0
@@ -1,474 +1,474 @@
1
- # tAPI Reference — Transparent Query API (Core)
2
-
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
-
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
-
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) |
11
-
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).
13
-
14
- ## 1. SP naming
15
-
16
- ```
17
- spAPI_[AUTH_]FunctionName
18
- ```
19
-
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` |
25
-
26
- ```sql
27
- spAPI_MessageHome -- có auth
28
- spAPI_AUTH_SelectChat -- không cần auth
29
- ```
30
-
31
- File API:
32
-
33
- ```
34
- spAPIFILE_Document -- SP để GET file
35
- spAPIFILE_UPLOAD_Document -- SP để nhận file upload
36
- ```
37
-
38
- Details: [tapi-file-api.md](tapi-file-api.md).
39
-
40
- ---
41
-
42
- ## 2. SP parameters
43
-
44
- ### `@url1_`, `@url2_`, `@url3_`... — route params
45
-
46
- Taken from URL segments in order:
47
-
48
- ```
49
- https://api.domain.vn/app/FunctionName/value1/value2
50
- ```
51
-
52
- ```sql
53
- @url1_GroupID varchar(50), -- nhận "value1"
54
- @url2_Mode varchar(50), -- nhận "value2"
55
- ```
56
-
57
- ### `@sys_` — system params (injected by tAPI, never sent by client)
58
-
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 |
70
-
71
- > **CRITICAL — never put `@sys_` in module.json `IN`.** tAPI fills all `@sys_*` from the session; client values are **ignored or overwritten**.
72
- >
73
- > ```json
74
- > // ❌ SAI — không đưa sys_ vào IN
75
- > "IN": { "ModuleID": "sModuleID", "sys_UserID": "vueData.user.UserID" }
76
- >
77
- > // ✅ ĐÚNG — chỉ truyền custom params
78
- > "IN": { "ModuleID": "sModuleID" }
79
- > ```
80
-
81
- #### `@sys_header_*` — read HTTP headers: replace each `-` with `_`
82
-
83
- Works for **any** header. `-` is invalid in SQL Server param names, so the literal name won't compile:
84
-
85
- ```sql
86
- -- ❌ SAI — lỗi cú pháp ngay lúc CREATE PROCEDURE, không phải lỗi runtime
87
- @sys_header_User-Agent varchar(500)
88
- ```
89
-
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.
92
-
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 |
100
-
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.
103
-
104
- ```sql
105
- CREATE PROCEDURE spAPI_AuditLog_Insert
106
- @Action nvarchar(100),
107
- @sys_UserID varchar(9),
108
- @sys_header_User_Agent nvarchar(500),
109
- @sys_header_X_Forwarded_For varchar(200)
110
- AS
111
- BEGIN
112
- -- ISNULL vì client không gửi header thì tham số là NULL, không phải chuỗi rỗng
113
- INSERT INTO tblAuditLog (Action, UserID, UserAgent, ClientIP, CreateTime)
114
- VALUES (@Action, @sys_UserID,
115
- ISNULL(@sys_header_User_Agent, N'(không có)'),
116
- ISNULL(@sys_header_X_Forwarded_For, ''),
117
- GETDATE());
118
- END
119
- ```
120
-
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).
124
-
125
- Check the name tAPI sees: `fui sp help <name>` (§3.1).
126
-
127
- ### Custom params — business params
128
-
129
- Param name = JSON body field name:
130
-
131
- ```sql
132
- @StudentID varchar(9),
133
- @SemesterID int,
134
- @Keyword nvarchar(200)
135
- ```
136
-
137
- #### Param binding is LENIENT — never errors
138
-
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.
143
-
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)`.
145
- >
146
- > ```sql
147
- > -- ❌ SAI
148
- > CREATE PROCEDURE spAPI_TS_Report
149
- > @HeID int = 99,
150
- > @NamHoc int = 2024,
151
- > @sys_UserID varchar(9) = null,
152
- > @sys_SystemRight int
153
- >
154
- > -- ✅ ĐÚNG
155
- > CREATE PROCEDURE spAPI_TS_Report
156
- > @HeID int,
157
- > @NamHoc int,
158
- > @sys_UserID varchar(9),
159
- > @sys_SystemRight int
160
- > ```
161
-
162
- Client sends: `POST /app/FunctionName` with body `{ "StudentID": "SV001", "SemesterID": 1 }`
163
-
164
- ---
165
-
166
- ## 3. API URL
167
-
168
- ```
169
- {domain}/{apiName}/{FunctionName}/{url1}/{url2}...
170
- {domain}/{apiName}/auth/{FunctionName}/{url1}/{url2}...
171
- ```
172
-
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_` |
180
-
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).
182
-
183
- ```
184
- SP: spAPI_AUTH_Select_Chat
185
- URL: https://api.example.vn/me/auth/Select_Chat
186
-
187
- SP: spAPI_GetStudentList
188
- URL: https://api.example.vn/me/GetStudentList
189
-
190
- SP: spAPI_GetStudentDetail (@url1_StudentID)
191
- URL: https://api.example.vn/me/GetStudentDetail/SV001
192
- ```
193
-
194
- > **CRITICAL — common bug: missing `/` between `domain` and `apiName`.** `domain` NEVER ends with `/`; insert exactly ONE `/` before `apiName`.
195
- >
196
- > ```
197
- > domain=tapi.lhu.edu.vn, apiName=ts, function=TS_Report_HeXetTuyenSelectAll
198
- >
199
- > ❌ SAI (thiếu /, "ts" bị nuốt vào domain): https://tapi.lhu.edu.vnts/TS_Report_HeXetTuyenSelectAll
200
- > ✅ ĐÚNG: https://tapi.lhu.edu.vn/ts/TS_Report_HeXetTuyenSelectAll
201
- > ```
202
- >
203
- > **Don't build URLs by hand/memory** — copy the `Wiring URL: ...` line from `fui sp verify` (or `fui sp help`) verbatim.
204
-
205
- ### 3.1 Param cache & `/help` route
206
-
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:
208
-
209
- ```
210
- {domain}/help/{apiName}/{FunctionName} ← xoá cache + trả về tham số của hàm
211
- {domain}/help/{apiName}/auth/{FunctionName}
212
- ```
213
-
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):
217
-
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.
221
-
222
- ---
223
-
224
- ## 4. Response format
225
-
226
- ### 4.1 One SELECT → array
227
-
228
- ```sql
229
- SELECT StudentID, FullName, GPA FROM tblStudent WHERE ClassID = @ClassID
230
- ```
231
-
232
- ```json
233
- {
234
- "data": [
235
- { "StudentID": "SV001", "FullName": "Nguyen Van A", "GPA": 3.5 },
236
- { "StudentID": "SV002", "FullName": "Tran Thi B", "GPA": 3.2 }
237
- ]
238
- }
239
- ```
240
-
241
- ### 4.2 Multiple SELECTs → array of arrays
242
-
243
- Each `SELECT` becomes one sub-array in `data`:
244
-
245
- ```sql
246
- SELECT TotalCount = COUNT(*) FROM tblStudent WHERE ClassID = @ClassID
247
- SELECT StudentID, FullName FROM tblStudent WHERE ClassID = @ClassID
248
- ```
249
-
250
- ```json
251
- {
252
- "data": [
253
- [{ "TotalCount": 42 }],
254
- [
255
- { "StudentID": "SV001", "FullName": "Nguyen Van A" },
256
- { "StudentID": "SV002", "FullName": "Tran Thi B" }
257
- ]
258
- ]
259
- }
260
- ```
261
-
262
- In module.json, read sub-arrays by index:
263
-
264
- ```json
265
- "OUT": "studentList",
266
- "CALLBACK": { "EXE": "vueData.total = vueData.studentList[0][0].TotalCount; vueData.students = vueData.studentList[1]" }
267
- ```
268
-
269
- ### 4.3 `convert_to_object` — row → object
270
-
271
- Returns an object instead of an array. **Many SELECTs** may use it; all merge into one response object.
272
-
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 `{}`.
274
-
275
- - `convert_to_object = 'key'` → nested object under that key
276
- - `convert_to_object = ''` → merged into root
277
-
278
- **Ex 1 — named key + root merge:**
279
-
280
- ```sql
281
- SELECT convert_to_object = 'sinhvien',
282
- sys_SystemRight = @sys_SystemRight,
283
- sys_FunctionRight = @sys_FunctionRight
284
-
285
- SELECT convert_to_object = '',
286
- ngaythang = GETDATE(), count = 10
287
- ```
288
-
289
- ```json
290
- {
291
- "sinhvien": {
292
- "sys_SystemRight": 9,
293
- "sys_FunctionRight": "[11][3]"
294
- },
295
- "ngaythang": "2018-09-11T11:20:47.397",
296
- "count": 10
297
- }
298
- ```
299
-
300
- **Ex 2 — `json_data` overrides same-name fields** (parsed and merged after SQL columns, so `json_data` wins, e.g. `ProjectName`):
301
-
302
- ```sql
303
- SELECT convert_to_object = '',
304
- ProjectName = N'abc',
305
- stt = 123,
306
- json_data = '{"ProjectName":"T-A-P-I","menu":[{"name":"Croatia","link":"vn"},{"name":"England","link":"/tuyensinh"}]}'
307
- ```
308
-
309
- ```json
310
- {
311
- "ProjectName": "T-A-P-I",
312
- "stt": 123,
313
- "menu": [
314
- { "name": "Croatia", "link": "vn" },
315
- { "name": "England", "link": "/tuyensinh" }
316
- ]
317
- }
318
- ```
319
-
320
- **Ex 3 — multiple `convert_to_object = ''` SELECTs all merge into root:**
321
-
322
- ```sql
323
- SELECT convert_to_object = '',
324
- ProjectName = N'abc', stt = 123,
325
- json_data = '{"ProjectName":"T-A-P-I","menu":[{"name":"Croatia","link":"vn"},{"name":"England","link":"/tuyensinh"}]}'
326
-
327
- SELECT convert_to_object = '',
328
- TruyVan2 = N'Dữ liệu của truy vấn 2',
329
- json_data = '{"TruyVan2_json_string": 99}'
330
- ```
331
-
332
- ```json
333
- {
334
- "ProjectName": "T-A-P-I",
335
- "stt": 123,
336
- "menu": [
337
- { "name": "Croatia", "link": "vn" },
338
- { "name": "England", "link": "/tuyensinh" }
339
- ],
340
- "TruyVan2": "Dữ liệu của truy vấn 2",
341
- "TruyVan2_json_string": 99
342
- }
343
- ```
344
-
345
- ### 4.4 `[json_data:FieldName]` — JSON string column → object
346
-
347
- Return a JSON-string column as an object:
348
-
349
- ```sql
350
- SELECT PartID, PartName, [json_data:FileInfo] = FileInfo
351
- FROM tblPart WHERE PartID = @url1_PartID
352
- ```
353
-
354
- tAPI parses it into a nested object:
355
-
356
- ```json
357
- {
358
- "PartID": "P001",
359
- "PartName": "Tài liệu",
360
- "FileInfo": { "fileName": "doc.pdf", "size": 1024 }
361
- }
362
- ```
363
-
364
- DB column stays a string. Name after `:` = response field; after `=` = real column.
365
-
366
- ---
367
-
368
- ## 5. Error handling in SP
369
-
370
- All errors use `RAISERROR` with severity **16**.
371
-
372
- ### Normal errors (validation, data constraints)
373
-
374
- ```sql
375
- RAISERROR(N'Dữ liệu không hợp lệ', 16, 1)
376
- RETURN
377
- ```
378
-
379
- ```json
380
- { "Message": "Dữ liệu không hợp lệ" }
381
- ```
382
-
383
- ### Unauthorized — prefix `[Unauthorized]` → HTTP 401
384
-
385
- ```sql
386
- IF @sys_SystemRight < 2
387
- BEGIN
388
- RAISERROR(N'[Unauthorized]Bạn không có quyền trên chức năng này.', 16, 1)
389
- RETURN
390
- END
391
- ```
392
-
393
- ```json
394
- { "Message": "Bạn không có quyền trên chức năng này." }
395
- ```
396
-
397
- **Mandatory:**
398
-
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).
405
-
406
- ---
407
-
408
- ## 6. SP design rules
409
-
410
- ### Naming
411
-
412
- ```
413
- spAPI_{Entity}{Action}
414
- ```
415
-
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) |
424
-
425
- ```sql
426
- spAPI_StudentList -- GET danh sách sinh viên
427
- spAPI_StudentGet -- GET chi tiết (url1 = StudentID)
428
- spAPI_StudentInsert -- POST thêm mới
429
- spAPI_StudentUpdate -- POST cập nhật
430
- spAPI_StudentDelete -- POST xoá (url1 = StudentID)
431
- spAPI_AUTH_CourseList -- Danh sách môn học công khai
432
- ```
433
-
434
- ### Wiring in FUI module.json
435
-
436
- ```json
437
- "fetchStudents": {
438
- "API": "/me/StudentList",
439
- "IN": { "Keyword": "vueData.keyword", "ClassID": "vueData.selectedClass" },
440
- "OUT": "studentList"
441
- },
442
- "addStudent": {
443
- "API": "/me/StudentInsert",
444
- "IN": {
445
- "StudentID": "vueData.form.StudentID",
446
- "FullName": "vueData.form.FullName",
447
- "ClassID": "vueData.form.ClassID"
448
- },
449
- "CALLBACK": [ { "CALL": "vueData.fetchStudents" }, { "MESS": "Đã thêm thành công" } ]
450
- },
451
- "deleteStudent": {
452
- "API": "/me/StudentDelete/`{{vueData.selectedID}}",
453
- "CONFIRM": "Bạn có chắc muốn xoá?",
454
- "CALLBACK": { "CALL": "vueData.fetchStudents" }
455
- }
456
- ```
457
-
458
- ---
459
-
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.
1
+ # tAPI Reference — Transparent Query API (Core)
2
+
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
+
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
+
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) |
11
+
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).
13
+
14
+ ## 1. SP naming
15
+
16
+ ```
17
+ spAPI_[AUTH_]FunctionName
18
+ ```
19
+
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` |
25
+
26
+ ```sql
27
+ spAPI_MessageHome -- có auth
28
+ spAPI_AUTH_SelectChat -- không cần auth
29
+ ```
30
+
31
+ File API:
32
+
33
+ ```
34
+ spAPIFILE_Document -- SP để GET file
35
+ spAPIFILE_UPLOAD_Document -- SP để nhận file upload
36
+ ```
37
+
38
+ Details: [tapi-file-api.md](tapi-file-api.md).
39
+
40
+ ---
41
+
42
+ ## 2. SP parameters
43
+
44
+ ### `@url1_`, `@url2_`, `@url3_`... — route params
45
+
46
+ Taken from URL segments in order:
47
+
48
+ ```
49
+ https://api.domain.vn/app/FunctionName/value1/value2
50
+ ```
51
+
52
+ ```sql
53
+ @url1_GroupID varchar(50), -- nhận "value1"
54
+ @url2_Mode varchar(50), -- nhận "value2"
55
+ ```
56
+
57
+ ### `@sys_` — system params (injected by tAPI, never sent by client)
58
+
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 |
70
+
71
+ > **CRITICAL — never put `@sys_` in module.json `IN`.** tAPI fills all `@sys_*` from the session; client values are **ignored or overwritten**.
72
+ >
73
+ > ```json
74
+ > // ❌ SAI — không đưa sys_ vào IN
75
+ > "IN": { "ModuleID": "sModuleID", "sys_UserID": "vueData.user.UserID" }
76
+ >
77
+ > // ✅ ĐÚNG — chỉ truyền custom params
78
+ > "IN": { "ModuleID": "sModuleID" }
79
+ > ```
80
+
81
+ #### `@sys_header_*` — read HTTP headers: replace each `-` with `_`
82
+
83
+ Works for **any** header. `-` is invalid in SQL Server param names, so the literal name won't compile:
84
+
85
+ ```sql
86
+ -- ❌ SAI — lỗi cú pháp ngay lúc CREATE PROCEDURE, không phải lỗi runtime
87
+ @sys_header_User-Agent varchar(500)
88
+ ```
89
+
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.
92
+
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 |
100
+
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.
103
+
104
+ ```sql
105
+ CREATE PROCEDURE spAPI_AuditLog_Insert
106
+ @Action nvarchar(100),
107
+ @sys_UserID varchar(9),
108
+ @sys_header_User_Agent nvarchar(500),
109
+ @sys_header_X_Forwarded_For varchar(200)
110
+ AS
111
+ BEGIN
112
+ -- ISNULL vì client không gửi header thì tham số là NULL, không phải chuỗi rỗng
113
+ INSERT INTO tblAuditLog (Action, UserID, UserAgent, ClientIP, CreateTime)
114
+ VALUES (@Action, @sys_UserID,
115
+ ISNULL(@sys_header_User_Agent, N'(không có)'),
116
+ ISNULL(@sys_header_X_Forwarded_For, ''),
117
+ GETDATE());
118
+ END
119
+ ```
120
+
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).
124
+
125
+ Check the name tAPI sees: `fui sp help <name>` (§3.1).
126
+
127
+ ### Custom params — business params
128
+
129
+ Param name = JSON body field name:
130
+
131
+ ```sql
132
+ @StudentID varchar(9),
133
+ @SemesterID int,
134
+ @Keyword nvarchar(200)
135
+ ```
136
+
137
+ #### Param binding is LENIENT — never errors
138
+
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.
143
+
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_*`; `fui sp verify` warns earlier. Set defaults **in the body**: `SET @HeID = ISNULL(@HeID, 99)`.
145
+ >
146
+ > ```sql
147
+ > -- ❌ SAI
148
+ > CREATE PROCEDURE spAPI_TS_Report
149
+ > @HeID int = 99,
150
+ > @NamHoc int = 2024,
151
+ > @sys_UserID varchar(9) = null,
152
+ > @sys_SystemRight int
153
+ >
154
+ > -- ✅ ĐÚNG
155
+ > CREATE PROCEDURE spAPI_TS_Report
156
+ > @HeID int,
157
+ > @NamHoc int,
158
+ > @sys_UserID varchar(9),
159
+ > @sys_SystemRight int
160
+ > ```
161
+
162
+ Client sends: `POST /app/FunctionName` with body `{ "StudentID": "SV001", "SemesterID": 1 }`
163
+
164
+ ---
165
+
166
+ ## 3. API URL
167
+
168
+ ```
169
+ {domain}/{apiName}/{FunctionName}/{url1}/{url2}...
170
+ {domain}/{apiName}/auth/{FunctionName}/{url1}/{url2}...
171
+ ```
172
+
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_` |
180
+
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).
182
+
183
+ ```
184
+ SP: spAPI_AUTH_Select_Chat
185
+ URL: https://api.example.vn/me/auth/Select_Chat
186
+
187
+ SP: spAPI_GetStudentList
188
+ URL: https://api.example.vn/me/GetStudentList
189
+
190
+ SP: spAPI_GetStudentDetail (@url1_StudentID)
191
+ URL: https://api.example.vn/me/GetStudentDetail/SV001
192
+ ```
193
+
194
+ > **CRITICAL — common bug: missing `/` between `domain` and `apiName`.** `domain` NEVER ends with `/`; insert exactly ONE `/` before `apiName`.
195
+ >
196
+ > ```
197
+ > domain=tapi.lhu.edu.vn, apiName=ts, function=TS_Report_HeXetTuyenSelectAll
198
+ >
199
+ > ❌ SAI (thiếu /, "ts" bị nuốt vào domain): https://tapi.lhu.edu.vnts/TS_Report_HeXetTuyenSelectAll
200
+ > ✅ ĐÚNG: https://tapi.lhu.edu.vn/ts/TS_Report_HeXetTuyenSelectAll
201
+ > ```
202
+ >
203
+ > **Don't build URLs by hand/memory** — copy the `url (reference only, not called; copy verbatim): ...` line from `fui sp verify` (or `fui sp help`) verbatim.
204
+
205
+ ### 3.1 Param cache & `/help` route
206
+
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:
208
+
209
+ ```
210
+ {domain}/help/{apiName}/{FunctionName} ← xoá cache + trả về tham số của hàm
211
+ {domain}/help/{apiName}/auth/{FunctionName}
212
+ ```
213
+
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):
217
+
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.
221
+
222
+ ---
223
+
224
+ ## 4. Response format
225
+
226
+ ### 4.1 One SELECT → array
227
+
228
+ ```sql
229
+ SELECT StudentID, FullName, GPA FROM tblStudent WHERE ClassID = @ClassID
230
+ ```
231
+
232
+ ```json
233
+ {
234
+ "data": [
235
+ { "StudentID": "SV001", "FullName": "Nguyen Van A", "GPA": 3.5 },
236
+ { "StudentID": "SV002", "FullName": "Tran Thi B", "GPA": 3.2 }
237
+ ]
238
+ }
239
+ ```
240
+
241
+ ### 4.2 Multiple SELECTs → array of arrays
242
+
243
+ Each `SELECT` becomes one sub-array in `data`:
244
+
245
+ ```sql
246
+ SELECT TotalCount = COUNT(*) FROM tblStudent WHERE ClassID = @ClassID
247
+ SELECT StudentID, FullName FROM tblStudent WHERE ClassID = @ClassID
248
+ ```
249
+
250
+ ```json
251
+ {
252
+ "data": [
253
+ [{ "TotalCount": 42 }],
254
+ [
255
+ { "StudentID": "SV001", "FullName": "Nguyen Van A" },
256
+ { "StudentID": "SV002", "FullName": "Tran Thi B" }
257
+ ]
258
+ ]
259
+ }
260
+ ```
261
+
262
+ In module.json, read sub-arrays by index:
263
+
264
+ ```json
265
+ "OUT": "studentList",
266
+ "CALLBACK": { "EXE": "vueData.total = vueData.studentList[0][0].TotalCount; vueData.students = vueData.studentList[1]" }
267
+ ```
268
+
269
+ ### 4.3 `convert_to_object` — row → object
270
+
271
+ Returns an object instead of an array. **Many SELECTs** may use it; all merge into one response object.
272
+
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 `{}`.
274
+
275
+ - `convert_to_object = 'key'` → nested object under that key
276
+ - `convert_to_object = ''` → merged into root
277
+
278
+ **Ex 1 — named key + root merge:**
279
+
280
+ ```sql
281
+ SELECT convert_to_object = 'sinhvien',
282
+ sys_SystemRight = @sys_SystemRight,
283
+ sys_FunctionRight = @sys_FunctionRight
284
+
285
+ SELECT convert_to_object = '',
286
+ ngaythang = GETDATE(), count = 10
287
+ ```
288
+
289
+ ```json
290
+ {
291
+ "sinhvien": {
292
+ "sys_SystemRight": 9,
293
+ "sys_FunctionRight": "[11][3]"
294
+ },
295
+ "ngaythang": "2018-09-11T11:20:47.397",
296
+ "count": 10
297
+ }
298
+ ```
299
+
300
+ **Ex 2 — `json_data` overrides same-name fields** (parsed and merged after SQL columns, so `json_data` wins, e.g. `ProjectName`):
301
+
302
+ ```sql
303
+ SELECT convert_to_object = '',
304
+ ProjectName = N'abc',
305
+ stt = 123,
306
+ json_data = '{"ProjectName":"T-A-P-I","menu":[{"name":"Croatia","link":"vn"},{"name":"England","link":"/tuyensinh"}]}'
307
+ ```
308
+
309
+ ```json
310
+ {
311
+ "ProjectName": "T-A-P-I",
312
+ "stt": 123,
313
+ "menu": [
314
+ { "name": "Croatia", "link": "vn" },
315
+ { "name": "England", "link": "/tuyensinh" }
316
+ ]
317
+ }
318
+ ```
319
+
320
+ **Ex 3 — multiple `convert_to_object = ''` SELECTs all merge into root:**
321
+
322
+ ```sql
323
+ SELECT convert_to_object = '',
324
+ ProjectName = N'abc', stt = 123,
325
+ json_data = '{"ProjectName":"T-A-P-I","menu":[{"name":"Croatia","link":"vn"},{"name":"England","link":"/tuyensinh"}]}'
326
+
327
+ SELECT convert_to_object = '',
328
+ TruyVan2 = N'Dữ liệu của truy vấn 2',
329
+ json_data = '{"TruyVan2_json_string": 99}'
330
+ ```
331
+
332
+ ```json
333
+ {
334
+ "ProjectName": "T-A-P-I",
335
+ "stt": 123,
336
+ "menu": [
337
+ { "name": "Croatia", "link": "vn" },
338
+ { "name": "England", "link": "/tuyensinh" }
339
+ ],
340
+ "TruyVan2": "Dữ liệu của truy vấn 2",
341
+ "TruyVan2_json_string": 99
342
+ }
343
+ ```
344
+
345
+ ### 4.4 `[json_data:FieldName]` — JSON string column → object
346
+
347
+ Return a JSON-string column as an object:
348
+
349
+ ```sql
350
+ SELECT PartID, PartName, [json_data:FileInfo] = FileInfo
351
+ FROM tblPart WHERE PartID = @url1_PartID
352
+ ```
353
+
354
+ tAPI parses it into a nested object:
355
+
356
+ ```json
357
+ {
358
+ "PartID": "P001",
359
+ "PartName": "Tài liệu",
360
+ "FileInfo": { "fileName": "doc.pdf", "size": 1024 }
361
+ }
362
+ ```
363
+
364
+ DB column stays a string. Name after `:` = response field; after `=` = real column.
365
+
366
+ ---
367
+
368
+ ## 5. Error handling in SP
369
+
370
+ All errors use `RAISERROR` with severity **16**.
371
+
372
+ ### Normal errors (validation, data constraints)
373
+
374
+ ```sql
375
+ RAISERROR(N'Dữ liệu không hợp lệ', 16, 1)
376
+ RETURN
377
+ ```
378
+
379
+ ```json
380
+ { "Message": "Dữ liệu không hợp lệ" }
381
+ ```
382
+
383
+ ### Unauthorized — prefix `[Unauthorized]` → HTTP 401
384
+
385
+ ```sql
386
+ IF @sys_SystemRight < 2
387
+ BEGIN
388
+ RAISERROR(N'[Unauthorized]Bạn không có quyền trên chức năng này.', 16, 1)
389
+ RETURN
390
+ END
391
+ ```
392
+
393
+ ```json
394
+ { "Message": "Bạn không có quyền trên chức năng này." }
395
+ ```
396
+
397
+ **Mandatory:**
398
+
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).
405
+
406
+ ---
407
+
408
+ ## 6. SP design rules
409
+
410
+ ### Naming
411
+
412
+ ```
413
+ spAPI_{Entity}{Action}
414
+ ```
415
+
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) |
424
+
425
+ ```sql
426
+ spAPI_StudentList -- GET danh sách sinh viên
427
+ spAPI_StudentGet -- GET chi tiết (url1 = StudentID)
428
+ spAPI_StudentInsert -- POST thêm mới
429
+ spAPI_StudentUpdate -- POST cập nhật
430
+ spAPI_StudentDelete -- POST xoá (url1 = StudentID)
431
+ spAPI_AUTH_CourseList -- Danh sách môn học công khai
432
+ ```
433
+
434
+ ### Wiring in FUI module.json
435
+
436
+ ```json
437
+ "fetchStudents": {
438
+ "API": "/me/StudentList",
439
+ "IN": { "Keyword": "vueData.keyword", "ClassID": "vueData.selectedClass" },
440
+ "OUT": "studentList"
441
+ },
442
+ "addStudent": {
443
+ "API": "/me/StudentInsert",
444
+ "IN": {
445
+ "StudentID": "vueData.form.StudentID",
446
+ "FullName": "vueData.form.FullName",
447
+ "ClassID": "vueData.form.ClassID"
448
+ },
449
+ "CALLBACK": [ { "CALL": "vueData.fetchStudents" }, { "MESS": "Đã thêm thành công" } ]
450
+ },
451
+ "deleteStudent": {
452
+ "API": "/me/StudentDelete/`{{vueData.selectedID}}",
453
+ "CONFIRM": "Bạn có chắc muốn xoá?",
454
+ "CALLBACK": { "CALL": "vueData.fetchStudents" }
455
+ }
456
+ ```
457
+
458
+ ---
459
+
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.