@ketvietlab/ketsuite 0.1.3 → 0.1.6

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 (359) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/deployment.d.ts.map +1 -1
  3. package/dist/deployment.js +3 -0
  4. package/dist/deployment.js.map +1 -1
  5. package/dist/index.d.ts +8 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +7 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/modules/account_staff_channel/channel-routes.d.ts.map +1 -1
  10. package/dist/modules/account_staff_channel/channel-routes.js +1 -12
  11. package/dist/modules/account_staff_channel/channel-routes.js.map +1 -1
  12. package/dist/modules/attendance/channel-routes.d.ts.map +1 -1
  13. package/dist/modules/attendance/channel-routes.js +1 -12
  14. package/dist/modules/attendance/channel-routes.js.map +1 -1
  15. package/dist/modules/backend/design/HANDOFF.md +2 -2
  16. package/dist/modules/backend/design/auth.css +12 -0
  17. package/dist/modules/backend/design/client/table-selection.d.mts +3 -3
  18. package/dist/modules/backend/design/client/table-selection.d.mts.map +1 -1
  19. package/dist/modules/backend/design/client/table-selection.mjs +1 -1
  20. package/dist/modules/backend/design/client/table-selection.mjs.map +4 -4
  21. package/dist/modules/backend/design/content.css +43 -0
  22. package/dist/modules/backend/design/design-system.css +1 -1
  23. package/dist/modules/backend/design/forms.css +84 -1
  24. package/dist/modules/backend/design/lists.css +26 -18
  25. package/dist/modules/backend/design/record.css +5 -2
  26. package/dist/modules/backend/design/responsive.css +5 -6
  27. package/dist/modules/backend/design/user-workflow.css +257 -0
  28. package/dist/modules/backend/forms.d.ts +38 -1
  29. package/dist/modules/backend/forms.d.ts.map +1 -1
  30. package/dist/modules/backend/forms.js +29 -0
  31. package/dist/modules/backend/forms.js.map +1 -1
  32. package/dist/modules/backend/index.d.ts +3 -2
  33. package/dist/modules/backend/index.d.ts.map +1 -1
  34. package/dist/modules/backend/index.js +3 -2
  35. package/dist/modules/backend/index.js.map +1 -1
  36. package/dist/modules/backend/messages.d.ts.map +1 -1
  37. package/dist/modules/backend/messages.js +42 -2
  38. package/dist/modules/backend/messages.js.map +1 -1
  39. package/dist/modules/channel_api/core.d.ts +30 -0
  40. package/dist/modules/channel_api/core.d.ts.map +1 -1
  41. package/dist/modules/channel_api/core.js +28 -0
  42. package/dist/modules/channel_api/core.js.map +1 -1
  43. package/dist/modules/crm/functions.d.ts +5 -0
  44. package/dist/modules/crm/functions.d.ts.map +1 -1
  45. package/dist/modules/crm/functions.js +105 -10
  46. package/dist/modules/crm/functions.js.map +1 -1
  47. package/dist/modules/crm/jobs.d.ts.map +1 -1
  48. package/dist/modules/crm/jobs.js +1 -0
  49. package/dist/modules/crm/jobs.js.map +1 -1
  50. package/dist/modules/crm/operations.d.ts.map +1 -1
  51. package/dist/modules/crm/operations.js +2 -1
  52. package/dist/modules/crm/operations.js.map +1 -1
  53. package/dist/modules/crm_backend/index.d.ts.map +1 -1
  54. package/dist/modules/crm_backend/index.js +5 -2
  55. package/dist/modules/crm_backend/index.js.map +1 -1
  56. package/dist/modules/crm_backend/messages.d.ts +116 -0
  57. package/dist/modules/crm_backend/messages.d.ts.map +1 -1
  58. package/dist/modules/crm_backend/messages.js +122 -6
  59. package/dist/modules/crm_backend/messages.js.map +1 -1
  60. package/dist/modules/crm_backend/routes.d.ts.map +1 -1
  61. package/dist/modules/crm_backend/routes.js +434 -174
  62. package/dist/modules/crm_backend/routes.js.map +1 -1
  63. package/dist/modules/crm_backend/screens/case-create.d.ts +7 -0
  64. package/dist/modules/crm_backend/screens/case-create.d.ts.map +1 -1
  65. package/dist/modules/crm_backend/screens/case-create.js +13 -4
  66. package/dist/modules/crm_backend/screens/case-create.js.map +1 -1
  67. package/dist/modules/crm_backend/screens/case-detail.d.ts +6 -0
  68. package/dist/modules/crm_backend/screens/case-detail.d.ts.map +1 -1
  69. package/dist/modules/crm_backend/screens/case-detail.js +73 -23
  70. package/dist/modules/crm_backend/screens/case-detail.js.map +1 -1
  71. package/dist/modules/crm_backend/screens/configuration.d.ts +21 -3
  72. package/dist/modules/crm_backend/screens/configuration.d.ts.map +1 -1
  73. package/dist/modules/crm_backend/screens/configuration.js +260 -55
  74. package/dist/modules/crm_backend/screens/configuration.js.map +1 -1
  75. package/dist/modules/crm_backend/screens/index.d.ts +3 -3
  76. package/dist/modules/crm_backend/screens/index.d.ts.map +1 -1
  77. package/dist/modules/crm_backend/screens/index.js +2 -2
  78. package/dist/modules/crm_backend/screens/index.js.map +1 -1
  79. package/dist/modules/crm_staff_channel/channel-routes.d.ts.map +1 -1
  80. package/dist/modules/crm_staff_channel/channel-routes.js +1 -12
  81. package/dist/modules/crm_staff_channel/channel-routes.js.map +1 -1
  82. package/dist/modules/flow/membership.d.ts.map +1 -1
  83. package/dist/modules/flow/membership.js +7 -1
  84. package/dist/modules/flow/membership.js.map +1 -1
  85. package/dist/modules/flow/operations.d.ts +10 -4
  86. package/dist/modules/flow/operations.d.ts.map +1 -1
  87. package/dist/modules/flow/operations.js +10 -4
  88. package/dist/modules/flow/operations.js.map +1 -1
  89. package/dist/modules/flow_staff_channel/channel-routes.d.ts.map +1 -1
  90. package/dist/modules/flow_staff_channel/channel-routes.js +1 -20
  91. package/dist/modules/flow_staff_channel/channel-routes.js.map +1 -1
  92. package/dist/modules/hospitality_core/messages.d.ts.map +1 -1
  93. package/dist/modules/hospitality_core/messages.js +44 -0
  94. package/dist/modules/hospitality_core/messages.js.map +1 -1
  95. package/dist/modules/hospitality_core/night-audit.d.ts.map +1 -1
  96. package/dist/modules/hospitality_core/night-audit.js +7 -0
  97. package/dist/modules/hospitality_core/night-audit.js.map +1 -1
  98. package/dist/modules/hospitality_core/operations.d.ts.map +1 -1
  99. package/dist/modules/hospitality_core/operations.js +150 -40
  100. package/dist/modules/hospitality_core/operations.js.map +1 -1
  101. package/dist/modules/hospitality_core/routes.d.ts.map +1 -1
  102. package/dist/modules/hospitality_core/routes.js +24 -2
  103. package/dist/modules/hospitality_core/routes.js.map +1 -1
  104. package/dist/modules/hospitality_core/screens/check-out-prep.d.ts +46 -0
  105. package/dist/modules/hospitality_core/screens/check-out-prep.d.ts.map +1 -0
  106. package/dist/modules/hospitality_core/screens/check-out-prep.js +133 -0
  107. package/dist/modules/hospitality_core/screens/check-out-prep.js.map +1 -0
  108. package/dist/modules/hospitality_core/screens/folio-detail.d.ts.map +1 -1
  109. package/dist/modules/hospitality_core/screens/folio-detail.js +2 -4
  110. package/dist/modules/hospitality_core/screens/folio-detail.js.map +1 -1
  111. package/dist/modules/hospitality_core/screens/index.d.ts +2 -0
  112. package/dist/modules/hospitality_core/screens/index.d.ts.map +1 -1
  113. package/dist/modules/hospitality_core/screens/index.js +1 -0
  114. package/dist/modules/hospitality_core/screens/index.js.map +1 -1
  115. package/dist/modules/hospitality_core/screens/reservation-detail.d.ts.map +1 -1
  116. package/dist/modules/hospitality_core/screens/reservation-detail.js +8 -1
  117. package/dist/modules/hospitality_core/screens/reservation-detail.js.map +1 -1
  118. package/dist/modules/hospitality_core/screens/shared.d.ts +12 -0
  119. package/dist/modules/hospitality_core/screens/shared.d.ts.map +1 -1
  120. package/dist/modules/hospitality_core/screens/shared.js +25 -4
  121. package/dist/modules/hospitality_core/screens/shared.js.map +1 -1
  122. package/dist/modules/hospitality_core/services.d.ts +48 -0
  123. package/dist/modules/hospitality_core/services.d.ts.map +1 -1
  124. package/dist/modules/hospitality_core/services.js +70 -0
  125. package/dist/modules/hospitality_core/services.js.map +1 -1
  126. package/dist/modules/hospitality_staff_channel/channel-routes.d.ts.map +1 -1
  127. package/dist/modules/hospitality_staff_channel/channel-routes.js +1 -12
  128. package/dist/modules/hospitality_staff_channel/channel-routes.js.map +1 -1
  129. package/dist/modules/inventory_staff_channel/channel-routes.d.ts.map +1 -1
  130. package/dist/modules/inventory_staff_channel/channel-routes.js +1 -12
  131. package/dist/modules/inventory_staff_channel/channel-routes.js.map +1 -1
  132. package/dist/modules/loyalty/admin-functions.d.ts +2 -0
  133. package/dist/modules/loyalty/admin-functions.d.ts.map +1 -1
  134. package/dist/modules/loyalty/admin-functions.js +96 -20
  135. package/dist/modules/loyalty/admin-functions.js.map +1 -1
  136. package/dist/modules/loyalty/engine.d.ts +4 -1
  137. package/dist/modules/loyalty/engine.d.ts.map +1 -1
  138. package/dist/modules/loyalty/engine.js +125 -18
  139. package/dist/modules/loyalty/engine.js.map +1 -1
  140. package/dist/modules/loyalty/index.d.ts.map +1 -1
  141. package/dist/modules/loyalty/index.js +4 -3
  142. package/dist/modules/loyalty/index.js.map +1 -1
  143. package/dist/modules/loyalty/jobs.d.ts.map +1 -1
  144. package/dist/modules/loyalty/jobs.js +0 -1
  145. package/dist/modules/loyalty/jobs.js.map +1 -1
  146. package/dist/modules/loyalty/membership-functions.d.ts +5 -0
  147. package/dist/modules/loyalty/membership-functions.d.ts.map +1 -1
  148. package/dist/modules/loyalty/membership-functions.js +37 -23
  149. package/dist/modules/loyalty/membership-functions.js.map +1 -1
  150. package/dist/modules/loyalty/messages.d.ts +14 -0
  151. package/dist/modules/loyalty/messages.d.ts.map +1 -1
  152. package/dist/modules/loyalty/messages.js +14 -0
  153. package/dist/modules/loyalty/messages.js.map +1 -1
  154. package/dist/modules/loyalty/models.d.ts.map +1 -1
  155. package/dist/modules/loyalty/models.js +17 -0
  156. package/dist/modules/loyalty/models.js.map +1 -1
  157. package/dist/modules/loyalty/order-functions.d.ts.map +1 -1
  158. package/dist/modules/loyalty/order-functions.js +70 -17
  159. package/dist/modules/loyalty/order-functions.js.map +1 -1
  160. package/dist/modules/loyalty/program-design.d.ts +11 -0
  161. package/dist/modules/loyalty/program-design.d.ts.map +1 -0
  162. package/dist/modules/loyalty/program-design.js +356 -0
  163. package/dist/modules/loyalty/program-design.js.map +1 -0
  164. package/dist/modules/loyalty/stats.d.ts +0 -13
  165. package/dist/modules/loyalty/stats.d.ts.map +1 -1
  166. package/dist/modules/loyalty/stats.js +11 -6
  167. package/dist/modules/loyalty/stats.js.map +1 -1
  168. package/dist/modules/loyalty/stored-value.d.ts.map +1 -1
  169. package/dist/modules/loyalty/stored-value.js +7 -2
  170. package/dist/modules/loyalty/stored-value.js.map +1 -1
  171. package/dist/modules/loyalty/types.d.ts +1 -1
  172. package/dist/modules/loyalty/types.d.ts.map +1 -1
  173. package/dist/modules/loyalty_backend/design-messages.d.ts +221 -0
  174. package/dist/modules/loyalty_backend/design-messages.d.ts.map +1 -0
  175. package/dist/modules/loyalty_backend/design-messages.js +221 -0
  176. package/dist/modules/loyalty_backend/design-messages.js.map +1 -0
  177. package/dist/modules/loyalty_backend/index.js +224 -412
  178. package/dist/modules/loyalty_backend/index.js.map +1 -1
  179. package/dist/modules/loyalty_backend/messages.d.ts +642 -214
  180. package/dist/modules/loyalty_backend/messages.d.ts.map +1 -1
  181. package/dist/modules/loyalty_backend/messages.js +19 -13
  182. package/dist/modules/loyalty_backend/messages.js.map +1 -1
  183. package/dist/modules/loyalty_backend/program-workspace.d.ts +4 -0
  184. package/dist/modules/loyalty_backend/program-workspace.d.ts.map +1 -0
  185. package/dist/modules/loyalty_backend/program-workspace.js +600 -0
  186. package/dist/modules/loyalty_backend/program-workspace.js.map +1 -0
  187. package/dist/modules/loyalty_backend/screens/index.d.ts +12 -36
  188. package/dist/modules/loyalty_backend/screens/index.d.ts.map +1 -1
  189. package/dist/modules/loyalty_backend/screens/index.js +148 -292
  190. package/dist/modules/loyalty_backend/screens/index.js.map +1 -1
  191. package/dist/modules/loyalty_backend/screens/page-frame.d.ts.map +1 -1
  192. package/dist/modules/loyalty_backend/screens/page-frame.js +2 -2
  193. package/dist/modules/loyalty_backend/screens/page-frame.js.map +1 -1
  194. package/dist/modules/loyalty_pos/functions.d.ts.map +1 -1
  195. package/dist/modules/loyalty_pos/functions.js +1 -0
  196. package/dist/modules/loyalty_pos/functions.js.map +1 -1
  197. package/dist/modules/loyalty_pos/index.js +1 -1
  198. package/dist/modules/loyalty_pos/index.js.map +1 -1
  199. package/dist/modules/loyalty_sale/functions.d.ts +1 -0
  200. package/dist/modules/loyalty_sale/functions.d.ts.map +1 -1
  201. package/dist/modules/loyalty_sale/functions.js +1 -0
  202. package/dist/modules/loyalty_sale/functions.js.map +1 -1
  203. package/dist/modules/partner_backend/client/partner.css +3 -1
  204. package/dist/modules/partner_backend/index.d.ts.map +1 -1
  205. package/dist/modules/partner_backend/index.js +4 -0
  206. package/dist/modules/partner_backend/index.js.map +1 -1
  207. package/dist/modules/partner_backend/routes.d.ts.map +1 -1
  208. package/dist/modules/partner_backend/routes.js +6 -1
  209. package/dist/modules/partner_backend/routes.js.map +1 -1
  210. package/dist/modules/partner_backend/screens/form.d.ts +1 -0
  211. package/dist/modules/partner_backend/screens/form.d.ts.map +1 -1
  212. package/dist/modules/partner_backend/screens/form.js +1 -0
  213. package/dist/modules/partner_backend/screens/form.js.map +1 -1
  214. package/dist/modules/purchase_staff_channel/order-routes.d.ts.map +1 -1
  215. package/dist/modules/purchase_staff_channel/order-routes.js +1 -12
  216. package/dist/modules/purchase_staff_channel/order-routes.js.map +1 -1
  217. package/dist/modules/quality_staff_channel/channel-routes.d.ts.map +1 -1
  218. package/dist/modules/quality_staff_channel/channel-routes.js +1 -12
  219. package/dist/modules/quality_staff_channel/channel-routes.js.map +1 -1
  220. package/dist/modules/sale/functions.d.ts.map +1 -1
  221. package/dist/modules/sale/functions.js +27 -17
  222. package/dist/modules/sale/functions.js.map +1 -1
  223. package/dist/modules/sale_staff_channel/channel-routes.d.ts.map +1 -1
  224. package/dist/modules/sale_staff_channel/channel-routes.js +1 -12
  225. package/dist/modules/sale_staff_channel/channel-routes.js.map +1 -1
  226. package/dist/modules/sale_staff_channel/order-routes.d.ts.map +1 -1
  227. package/dist/modules/sale_staff_channel/order-routes.js +1 -12
  228. package/dist/modules/sale_staff_channel/order-routes.js.map +1 -1
  229. package/dist/modules/stock_staff_channel/operation-routes.d.ts.map +1 -1
  230. package/dist/modules/stock_staff_channel/operation-routes.js +1 -12
  231. package/dist/modules/stock_staff_channel/operation-routes.js.map +1 -1
  232. package/dist/modules/user/authorization.d.ts +64 -1
  233. package/dist/modules/user/authorization.d.ts.map +1 -1
  234. package/dist/modules/user/authorization.js +445 -53
  235. package/dist/modules/user/authorization.js.map +1 -1
  236. package/dist/modules/user/functions.d.ts.map +1 -1
  237. package/dist/modules/user/functions.js +45 -23
  238. package/dist/modules/user/functions.js.map +1 -1
  239. package/dist/modules/user/index.d.ts +2 -1
  240. package/dist/modules/user/index.d.ts.map +1 -1
  241. package/dist/modules/user/index.js +1 -1
  242. package/dist/modules/user/index.js.map +1 -1
  243. package/dist/modules/user/models.d.ts.map +1 -1
  244. package/dist/modules/user/models.js +5 -0
  245. package/dist/modules/user/models.js.map +1 -1
  246. package/dist/modules/user/roles.d.ts.map +1 -1
  247. package/dist/modules/user/roles.js +2 -1
  248. package/dist/modules/user/roles.js.map +1 -1
  249. package/dist/modules/user_backend/access-routes.d.ts +31 -0
  250. package/dist/modules/user_backend/access-routes.d.ts.map +1 -0
  251. package/dist/modules/user_backend/access-routes.js +447 -0
  252. package/dist/modules/user_backend/access-routes.js.map +1 -0
  253. package/dist/modules/user_backend/index.d.ts +1 -0
  254. package/dist/modules/user_backend/index.d.ts.map +1 -1
  255. package/dist/modules/user_backend/index.js +1 -0
  256. package/dist/modules/user_backend/index.js.map +1 -1
  257. package/dist/modules/user_backend/messages.d.ts +4 -0
  258. package/dist/modules/user_backend/messages.d.ts.map +1 -1
  259. package/dist/modules/user_backend/messages.js +4 -0
  260. package/dist/modules/user_backend/messages.js.map +1 -1
  261. package/dist/modules/user_backend/routes.d.ts.map +1 -1
  262. package/dist/modules/user_backend/routes.js +38 -3
  263. package/dist/modules/user_backend/routes.js.map +1 -1
  264. package/dist/modules/user_backend/screens/user-form.d.ts +1 -0
  265. package/dist/modules/user_backend/screens/user-form.d.ts.map +1 -1
  266. package/dist/modules/user_backend/screens/user-form.js +28 -0
  267. package/dist/modules/user_backend/screens/user-form.js.map +1 -1
  268. package/dist/modules/website/cms.d.ts.map +1 -1
  269. package/dist/modules/website/cms.js +283 -6
  270. package/dist/modules/website/cms.js.map +1 -1
  271. package/dist/modules/website/index.d.ts.map +1 -1
  272. package/dist/modules/website/index.js +12 -0
  273. package/dist/modules/website/index.js.map +1 -1
  274. package/dist/modules/website/media-usage.d.ts +38 -0
  275. package/dist/modules/website/media-usage.d.ts.map +1 -0
  276. package/dist/modules/website/media-usage.js +87 -0
  277. package/dist/modules/website/media-usage.js.map +1 -0
  278. package/dist/modules/website/paths.js +1 -1
  279. package/dist/modules/website/paths.js.map +1 -1
  280. package/dist/modules/website/sections.d.ts.map +1 -1
  281. package/dist/modules/website/sections.js +12 -1
  282. package/dist/modules/website/sections.js.map +1 -1
  283. package/dist/modules/website_backend/index.js +138 -0
  284. package/dist/modules/website_backend/index.js.map +1 -1
  285. package/dist/modules/website_backend/menus.d.ts.map +1 -1
  286. package/dist/modules/website_backend/menus.js +11 -0
  287. package/dist/modules/website_backend/menus.js.map +1 -1
  288. package/dist/modules/website_backend/routes.d.ts.map +1 -1
  289. package/dist/modules/website_backend/routes.js +414 -31
  290. package/dist/modules/website_backend/routes.js.map +1 -1
  291. package/dist/modules/website_backend/screens/index.d.ts +98 -6
  292. package/dist/modules/website_backend/screens/index.d.ts.map +1 -1
  293. package/dist/modules/website_backend/screens/index.js +600 -154
  294. package/dist/modules/website_backend/screens/index.js.map +1 -1
  295. package/dist/modules/website_backend/screens/page-frame.d.ts +3 -1
  296. package/dist/modules/website_backend/screens/page-frame.d.ts.map +1 -1
  297. package/dist/modules/website_backend/screens/page-frame.js +1 -1
  298. package/dist/modules/website_backend/screens/page-frame.js.map +1 -1
  299. package/dist/modules/website_menu/functions.d.ts.map +1 -1
  300. package/dist/modules/website_menu/functions.js +51 -0
  301. package/dist/modules/website_menu/functions.js.map +1 -1
  302. package/dist/modules/website_menu/index.js +2 -0
  303. package/dist/modules/website_menu/index.js.map +1 -1
  304. package/dist/modules/website_search/functions.d.ts.map +1 -1
  305. package/dist/modules/website_search/functions.js +10 -120
  306. package/dist/modules/website_search/functions.js.map +1 -1
  307. package/dist/modules/website_search/index.js +2 -0
  308. package/dist/modules/website_search/index.js.map +1 -1
  309. package/dist/modules/website_search/jobs.d.ts +3 -0
  310. package/dist/modules/website_search/jobs.d.ts.map +1 -0
  311. package/dist/modules/website_search/jobs.js +101 -0
  312. package/dist/modules/website_search/jobs.js.map +1 -0
  313. package/dist/modules/website_search/rebuild.d.ts +33 -0
  314. package/dist/modules/website_search/rebuild.d.ts.map +1 -0
  315. package/dist/modules/website_search/rebuild.js +125 -0
  316. package/dist/modules/website_search/rebuild.js.map +1 -0
  317. package/dist/permission-catalogue.d.ts.map +1 -1
  318. package/dist/permission-catalogue.js +21 -1
  319. package/dist/permission-catalogue.js.map +1 -1
  320. package/dist/scaffold/index.js +1 -1
  321. package/dist/ui/chrome.d.ts +8 -0
  322. package/dist/ui/chrome.d.ts.map +1 -1
  323. package/dist/ui/chrome.js +9 -1
  324. package/dist/ui/chrome.js.map +1 -1
  325. package/dist/ui/client/table-selection-view.d.ts.map +1 -1
  326. package/dist/ui/client/table-selection-view.js +161 -5
  327. package/dist/ui/client/table-selection-view.js.map +1 -1
  328. package/dist/ui/client/user-workflow.d.ts +2 -0
  329. package/dist/ui/client/user-workflow.d.ts.map +1 -0
  330. package/dist/ui/client/user-workflow.js +141 -0
  331. package/dist/ui/client/user-workflow.js.map +1 -0
  332. package/dist/ui/form.d.ts +9 -0
  333. package/dist/ui/form.d.ts.map +1 -1
  334. package/dist/ui/form.js +29 -15
  335. package/dist/ui/form.js.map +1 -1
  336. package/dist/ui/icons.d.ts.map +1 -1
  337. package/dist/ui/icons.js +2 -0
  338. package/dist/ui/icons.js.map +1 -1
  339. package/dist/ui/index.d.ts +7 -3
  340. package/dist/ui/index.d.ts.map +1 -1
  341. package/dist/ui/index.js +6 -3
  342. package/dist/ui/index.js.map +1 -1
  343. package/dist/ui/layout.d.ts.map +1 -1
  344. package/dist/ui/layout.js +14 -4
  345. package/dist/ui/layout.js.map +1 -1
  346. package/dist/ui/nav.d.ts.map +1 -1
  347. package/dist/ui/nav.js +2 -1
  348. package/dist/ui/nav.js.map +1 -1
  349. package/dist/ui/state.d.ts +20 -1
  350. package/dist/ui/state.d.ts.map +1 -1
  351. package/dist/ui/state.js +17 -0
  352. package/dist/ui/state.js.map +1 -1
  353. package/dist/ui/table.js +1 -1
  354. package/dist/ui/table.js.map +1 -1
  355. package/dist/ui/user-workflow.d.ts +72 -0
  356. package/dist/ui/user-workflow.d.ts.map +1 -0
  357. package/dist/ui/user-workflow.js +144 -0
  358. package/dist/ui/user-workflow.js.map +1 -0
  359. package/package.json +8 -4
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { asc, defineFn, deleteFrom, eq, from, inArray, isNotNull, KetError, permissionDigest, } from '@ketvietlab/ketjs';
2
+ import { and, desc, ilike, lt, or, isNull, defineFn, deleteFrom, eq, from, inArray, isNotNull, KetError, permissionDigest, } from '@ketvietlab/ketjs';
3
3
  const issue = (field, code, params) => ({
4
4
  field,
5
5
  code,
@@ -14,10 +14,10 @@ class AuthorizationAbort extends Error {
14
14
  this.result = result;
15
15
  }
16
16
  }
17
- const abort = (result) => {
17
+ export const abortAuthorization = (result) => {
18
18
  throw new AuthorizationAbort(result);
19
19
  };
20
- const authorizationTransaction = async (ctx, body) => {
20
+ export const authorizationTransaction = async (ctx, body) => {
21
21
  try {
22
22
  return await ctx.tx(body);
23
23
  }
@@ -130,11 +130,11 @@ const bumpRevision = async (ctx, expected) => {
130
130
  export const recordAuthorizationAudit = async (ctx, values) => {
131
131
  await ctx.db.insert('user.SecurityAudit', {
132
132
  id: randomUUID(),
133
- userId: null,
133
+ userId: values.userId ?? values.after?.userId ?? values.before?.userId ?? null,
134
134
  event: values.event,
135
135
  occurredAt: nowIso(),
136
136
  networkFingerprint: null,
137
- metadata: null,
137
+ metadata: values.metadata ?? { roleId: values.source, assignmentId: values.targetId },
138
138
  actorKey: ctx.actor,
139
139
  targetKind: values.targetKind,
140
140
  targetId: values.targetId,
@@ -212,14 +212,15 @@ export const managedRoleHealthIssues = (manifest, role, grants, sources) => {
212
212
  ? ['stale-managed-provenance']
213
213
  : [];
214
214
  };
215
- /** Resolve and explain the exact set used by request authorization. */
216
- export async function resolveEffectivePermissions(ctx, userId) {
215
+ export async function resolveEffectivePermissions(ctx, userId, projection = {}) {
216
+ const selectedCompany = projection.companyId === undefined ? ctx.scope.company : projection.companyId;
217
+ const selectedBranch = projection.branchId === undefined ? ctx.scope.branch : projection.branchId;
217
218
  const revision = await authorizationRevisionOf(ctx);
218
219
  const empty = (issues = []) => ({
219
220
  revision,
220
221
  context: {
221
- companyId: ctx.scope.company ?? null,
222
- branchId: ctx.scope.branch ?? null,
222
+ companyId: selectedCompany ?? null,
223
+ branchId: selectedBranch ?? null,
223
224
  },
224
225
  superuser: false,
225
226
  functions: [],
@@ -234,18 +235,18 @@ export async function resolveEffectivePermissions(ctx, userId) {
234
235
  if (expiresAt == null || expiresAt > Date.now())
235
236
  return { ...empty(), superuser: true };
236
237
  }
237
- const companyId = String(ctx.scope.company ?? '');
238
+ const companyId = String(selectedCompany ?? '');
238
239
  if (!companyId)
239
240
  return empty([{ code: 'invalid-company-context' }]);
240
- const companyIds = await activeCompanyMemberships(ctx, userId);
241
+ const companyIds = projection.companies ?? (await activeCompanyMemberships(ctx, userId));
241
242
  if (!companyIds.has(companyId))
242
243
  return empty([{ code: 'invalid-company-context' }]);
243
- const branchId = String(ctx.scope.branch ?? '');
244
- const branches = await activeBranchMemberships(ctx, userId, companyIds);
244
+ const branchId = String(selectedBranch ?? '');
245
+ const branches = projection.branches ?? (await activeBranchMemberships(ctx, userId, companyIds));
245
246
  if (branchId && branches.get(branchId) !== companyId)
246
247
  return empty([{ code: 'invalid-branch-context' }]);
247
248
  const A = ctx.table('user.Assignment');
248
- const assignments = (await ctx.db.all(from(A).where(eq(A.userId, userId)))).filter((assignment) => roleApplies(assignment, companyId, branchId));
249
+ const assignments = (projection.assignments ?? (await ctx.db.all(from(A).where(eq(A.userId, userId))))).filter((assignment) => roleApplies(assignment, companyId, branchId));
249
250
  if (!assignments.length)
250
251
  return empty();
251
252
  const roleIds = [...new Set(assignments.map((assignment) => String(assignment.roleId)))];
@@ -397,7 +398,7 @@ const roleTemplatePreview = async (ctx, roleId, templateKey) => {
397
398
  highRiskAdded,
398
399
  };
399
400
  };
400
- const AUTHORIZATION_EFFECTS = [
401
+ export const AUTHORIZATION_EFFECTS = [
401
402
  'read:user.User',
402
403
  'write:user.User',
403
404
  'read:user.Role',
@@ -446,7 +447,7 @@ export const authorizationFunctions = {
446
447
  }),
447
448
  }),
448
449
  effectiveAccess: defineFn({
449
- input: { userId: 'id' },
450
+ input: { userId: 'id', companyId: 'id?', branchId: 'id?' },
450
451
  output: {
451
452
  revision: 'int',
452
453
  context: 'json',
@@ -455,7 +456,10 @@ export const authorizationFunctions = {
455
456
  issues: 'json',
456
457
  },
457
458
  effects: AUTHORIZATION_EFFECTS.filter((effect) => !effect.startsWith('write:')),
458
- handler: (ctx, args) => resolveEffectivePermissions(ctx, String(args.userId)),
459
+ handler: (ctx, args) => resolveEffectivePermissions(ctx, String(args.userId), {
460
+ companyId: args.companyId,
461
+ branchId: args.branchId,
462
+ }),
459
463
  }),
460
464
  previewRoleTemplate: defineFn({
461
465
  input: { roleId: 'id?', templateKey: 'text' },
@@ -505,7 +509,7 @@ export const authorizationFunctions = {
505
509
  return authorizationTransaction(ctx, async (tx) => {
506
510
  const replay = await operationReplay(tx, operationId, args);
507
511
  if ('conflict' in replay)
508
- abort(invalid([issue('idempotencyKey', 'E_ROLE_TEMPLATE_CONFLICT')]));
512
+ abortAuthorization(invalid([issue('idempotencyKey', 'E_ROLE_TEMPLATE_CONFLICT')]));
509
513
  if ('replay' in replay && replay.replay)
510
514
  return {
511
515
  ...replay.result,
@@ -516,15 +520,15 @@ export const authorizationFunctions = {
516
520
  const role = await tx.db.one(from(R).where(eq(R.id, roleId)));
517
521
  const expectedRoleRevision = Number(args.expectedRoleRevision);
518
522
  if (Number(role?.revision ?? 0) !== expectedRoleRevision)
519
- abort(invalid([issue('expectedRoleRevision', 'E_ROLE_TEMPLATE_CONFLICT')]));
523
+ abortAuthorization(invalid([issue('expectedRoleRevision', 'E_ROLE_TEMPLATE_CONFLICT')]));
520
524
  if (role && String(role.mode ?? 'custom') !== 'managed')
521
- abort(invalid([issue('roleId', 'E_ROLE_TEMPLATE_CONFLICT')]));
525
+ abortAuthorization(invalid([issue('roleId', 'E_ROLE_TEMPLATE_CONFLICT')]));
522
526
  if (role && Number(role.templateVersion ?? 0) > template.version)
523
- abort(invalid([issue('templateKey', 'E_ROLE_TEMPLATE_STALE')]));
527
+ abortAuthorization(invalid([issue('templateKey', 'E_ROLE_TEMPLATE_STALE')]));
524
528
  if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
525
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
529
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
526
530
  const preview = await roleTemplatePreview(tx, roleId, templateKey);
527
- const validPreview = preview.ok ? preview : abort(preview);
531
+ const validPreview = preview.ok ? preview : abortAuthorization(preview);
528
532
  if (role &&
529
533
  role.templateKey === templateKey &&
530
534
  Number(role.templateVersion) === template.version &&
@@ -533,7 +537,7 @@ export const authorizationFunctions = {
533
537
  validPreview.managedAdded.length === 0 &&
534
538
  validPreview.managedRemoved.length === 0) {
535
539
  if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
536
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
540
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
537
541
  const result = {
538
542
  ok: true,
539
543
  roleId,
@@ -557,7 +561,7 @@ export const authorizationFunctions = {
557
561
  revision: 1,
558
562
  });
559
563
  if (!('dryRun' in inserted) && !inserted.inserted)
560
- abort(invalid([issue('roleId', 'E_ROLE_TEMPLATE_CONFLICT')]));
564
+ abortAuthorization(invalid([issue('roleId', 'E_ROLE_TEMPLATE_CONFLICT')]));
561
565
  }
562
566
  else {
563
567
  const changed = await tx.db.compareAndSet('user.Role', { id: roleId }, { revision: expectedRoleRevision }, {
@@ -570,7 +574,7 @@ export const authorizationFunctions = {
570
574
  revision: expectedRoleRevision + 1,
571
575
  });
572
576
  if (!('dryRun' in changed) && !changed.matched)
573
- abort(invalid([issue('expectedRoleRevision', 'E_ROLE_TEMPLATE_CONFLICT')]));
577
+ abortAuthorization(invalid([issue('expectedRoleRevision', 'E_ROLE_TEMPLATE_CONFLICT')]));
574
578
  }
575
579
  const S = tx.table('user.GrantSource');
576
580
  const G = tx.table('user.Grant');
@@ -606,7 +610,7 @@ export const authorizationFunctions = {
606
610
  await tx.db.del(deleteFrom(G).where(eq(G.roleId, roleId), eq(G.fnKey, fnKey)));
607
611
  }
608
612
  const revision = (await bumpRevision(tx, Number(args.expectedAuthorizationRevision))) ??
609
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
613
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
610
614
  const result = {
611
615
  ok: true,
612
616
  roleId,
@@ -665,13 +669,13 @@ export const authorizationFunctions = {
665
669
  return invalid([issue('reason', 'user.error.required')]);
666
670
  return authorizationTransaction(ctx, async (tx) => {
667
671
  const liveScope = await normalizeAssignmentScope(tx, String(args.userId), args);
668
- const scope = liveScope.ok ? liveScope.scope : abort(liveScope);
672
+ const scope = liveScope.ok ? liveScope.scope : abortAuthorization(liveScope);
669
673
  const replay = await operationReplay(tx, operationId, {
670
674
  ...args,
671
675
  scope,
672
676
  });
673
677
  if ('conflict' in replay)
674
- abort(invalid([issue('idempotencyKey', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
678
+ abortAuthorization(invalid([issue('idempotencyKey', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
675
679
  if ('replay' in replay && replay.replay)
676
680
  return {
677
681
  ...replay.result,
@@ -679,15 +683,15 @@ export const authorizationFunctions = {
679
683
  };
680
684
  const R = tx.table('user.Role');
681
685
  if (!(await tx.db.one(from(R).where(eq(R.id, args.roleId)))))
682
- abort(invalid([issue('roleId', 'user.error.roleMissing')]));
686
+ abortAuthorization(invalid([issue('roleId', 'user.error.roleMissing')]));
683
687
  if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
684
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
688
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
685
689
  const A = tx.table('user.Assignment');
686
690
  const held = await tx.db.one(from(A).where(eq(A.userId, args.userId), eq(A.roleId, args.roleId), eq(A.scopeKey, scope.scopeKey)));
687
691
  const assignmentId = String(held?.id ?? args.id);
688
692
  if (held) {
689
693
  if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
690
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
694
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
691
695
  const result = {
692
696
  ok: true,
693
697
  id: assignmentId,
@@ -705,7 +709,7 @@ export const authorizationFunctions = {
705
709
  ...scope,
706
710
  });
707
711
  const revision = (await bumpRevision(tx, Number(args.expectedAuthorizationRevision))) ??
708
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
712
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
709
713
  const result = {
710
714
  ok: true,
711
715
  id: assignmentId,
@@ -732,6 +736,7 @@ export const authorizationFunctions = {
732
736
  unassignScopedRole: defineFn({
733
737
  input: {
734
738
  userId: 'id',
739
+ assignmentId: 'id?',
735
740
  roleId: 'id',
736
741
  scopeKey: 'text',
737
742
  expectedAuthorizationRevision: 'int',
@@ -755,19 +760,24 @@ export const authorizationFunctions = {
755
760
  return authorizationTransaction(ctx, async (tx) => {
756
761
  const replay = await operationReplay(tx, operationId, args);
757
762
  if ('conflict' in replay)
758
- abort(invalid([issue('idempotencyKey', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
763
+ abortAuthorization(invalid([issue('idempotencyKey', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
759
764
  if ('replay' in replay && replay.replay)
760
765
  return {
761
766
  ...replay.result,
762
767
  replayed: true,
763
768
  };
764
769
  if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
765
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
770
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
766
771
  const A = tx.table('user.Assignment');
767
- const before = await tx.db.one(from(A).where(eq(A.userId, args.userId), eq(A.roleId, args.roleId), eq(A.scopeKey, args.scopeKey)));
772
+ let selected = from(A).where(eq(A.userId, args.userId), eq(A.roleId, args.roleId), args.scopeKey === 'tenant'
773
+ ? or(eq(A.scopeKey, 'tenant'), isNull(A.scopeKey))
774
+ : eq(A.scopeKey, args.scopeKey));
775
+ if (args.assignmentId)
776
+ selected = selected.where(eq(A.id, args.assignmentId));
777
+ const before = await tx.db.one(selected);
768
778
  if (!before) {
769
779
  if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
770
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
780
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
771
781
  const result = {
772
782
  ok: true,
773
783
  removed: 0,
@@ -777,9 +787,9 @@ export const authorizationFunctions = {
777
787
  await completeOperation(tx, operationId, result);
778
788
  return result;
779
789
  }
780
- const removed = (await tx.db.del(deleteFrom(A).where(eq(A.userId, args.userId), eq(A.roleId, args.roleId), eq(A.scopeKey, args.scopeKey)))).changes;
790
+ const removed = (await tx.db.del(deleteFrom(A).where(eq(A.id, before.id)))).changes;
781
791
  const revision = (await bumpRevision(tx, Number(args.expectedAuthorizationRevision))) ??
782
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
792
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
783
793
  const result = { ok: true, removed, revision, replayed: false };
784
794
  await recordAuthorizationAudit(tx, {
785
795
  event: 'authorization.assignment.removed',
@@ -824,7 +834,7 @@ export const authorizationFunctions = {
824
834
  return authorizationTransaction(ctx, async (tx) => {
825
835
  const replay = await operationReplay(tx, operationId, args);
826
836
  if ('conflict' in replay)
827
- abort(invalid([issue('idempotencyKey', 'E_ROLE_TEMPLATE_CONFLICT')]));
837
+ abortAuthorization(invalid([issue('idempotencyKey', 'E_ROLE_TEMPLATE_CONFLICT')]));
828
838
  if ('replay' in replay && replay.replay)
829
839
  return {
830
840
  ...replay.result,
@@ -834,9 +844,9 @@ export const authorizationFunctions = {
834
844
  const sourceRow = await tx.db.one(from(R).where(eq(R.id, args.sourceRoleId)));
835
845
  const source = sourceRow && String(sourceRow.mode ?? 'custom') === 'managed'
836
846
  ? sourceRow
837
- : abort(invalid([issue('sourceRoleId', 'E_ROLE_TEMPLATE_CONFLICT')]));
847
+ : abortAuthorization(invalid([issue('sourceRoleId', 'E_ROLE_TEMPLATE_CONFLICT')]));
838
848
  if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
839
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
849
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
840
850
  const inserted = await tx.db.insertIfAbsent('user.Role', {
841
851
  id: args.id,
842
852
  name,
@@ -848,7 +858,7 @@ export const authorizationFunctions = {
848
858
  revision: 1,
849
859
  });
850
860
  if (!('dryRun' in inserted) && !inserted.inserted)
851
- abort(invalid([issue('id', 'E_ROLE_TEMPLATE_CONFLICT')]));
861
+ abortAuthorization(invalid([issue('id', 'E_ROLE_TEMPLATE_CONFLICT')]));
852
862
  const G = tx.table('user.Grant');
853
863
  for (const grant of await tx.db.all(from(G).where(eq(G.roleId, args.sourceRoleId)))) {
854
864
  const fnKey = String(grant.fnKey);
@@ -867,7 +877,7 @@ export const authorizationFunctions = {
867
877
  });
868
878
  }
869
879
  const revision = (await bumpRevision(tx, Number(args.expectedAuthorizationRevision))) ??
870
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
880
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
871
881
  const result = { ok: true, id: args.id, revision, replayed: false };
872
882
  await recordAuthorizationAudit(tx, {
873
883
  event: 'authorization.role.cloned',
@@ -926,19 +936,19 @@ export const authorizationFunctions = {
926
936
  reason,
927
937
  });
928
938
  if ('conflict' in replay)
929
- abort(invalid([issue('idempotencyKey', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
939
+ abortAuthorization(invalid([issue('idempotencyKey', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
930
940
  if ('replay' in replay && replay.replay)
931
941
  return {
932
942
  ...replay.result,
933
943
  replayed: true,
934
944
  };
935
945
  if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
936
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
946
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
937
947
  const U = tx.table('user.User');
938
- const user = await tx.db.one(from(U).where(eq(U.id, args.userId), eq(U.active, true)));
948
+ const user = await tx.db.one(from(U).where(eq(U.id, args.userId)));
939
949
  if (!user || String(user.accessKind) !== 'internal')
940
- abort(invalid([issue('userId', 'user.error.userMissing')]));
941
- const target = user ?? abort(invalid([issue('userId', 'user.error.userMissing')]));
950
+ abortAuthorization(invalid([issue('userId', 'user.error.userMissing')]));
951
+ const target = user ?? abortAuthorization(invalid([issue('userId', 'user.error.userMissing')]));
942
952
  const after = enabled
943
953
  ? {
944
954
  superuser: true,
@@ -959,7 +969,7 @@ export const authorizationFunctions = {
959
969
  after.superuserExpiresAt;
960
970
  if (unchanged) {
961
971
  if ((await authorizationRevisionOf(tx)) !== Number(args.expectedAuthorizationRevision))
962
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
972
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
963
973
  const result = {
964
974
  ok: true,
965
975
  userId: String(args.userId),
@@ -973,7 +983,7 @@ export const authorizationFunctions = {
973
983
  }
974
984
  await tx.db.update('user.User', { id: args.userId }, after);
975
985
  const revision = (await bumpRevision(tx, Number(args.expectedAuthorizationRevision))) ??
976
- abort(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
986
+ abortAuthorization(invalid([issue('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT')]));
977
987
  const result = {
978
988
  ok: true,
979
989
  userId: String(args.userId),
@@ -1006,11 +1016,21 @@ export const authorizationFunctions = {
1006
1016
  },
1007
1017
  }),
1008
1018
  listAuthorizationAudit: defineFn({
1009
- input: { targetKind: 'text?', targetId: 'text?', limit: 'int?' },
1019
+ input: {
1020
+ id: 'id?',
1021
+ targetKind: 'text?',
1022
+ targetId: 'text?',
1023
+ userId: 'id?',
1024
+ limit: 'int?',
1025
+ beforeRevision: 'int?',
1026
+ beforeId: 'id?',
1027
+ },
1010
1028
  output: {
1011
1029
  id: 'id',
1012
1030
  event: 'text',
1013
1031
  occurredAt: 'datetime',
1032
+ userId: 'id?',
1033
+ metadata: 'json?',
1014
1034
  actorKey: 'text?',
1015
1035
  targetKind: 'text?',
1016
1036
  targetId: 'text?',
@@ -1027,7 +1047,15 @@ export const authorizationFunctions = {
1027
1047
  const A = ctx.table('user.SecurityAudit');
1028
1048
  let query = from(A)
1029
1049
  .where(isNotNull(A.authorizationRevision))
1030
- .orderBy(asc(A.authorizationRevision), asc(A.id));
1050
+ .orderBy(desc(A.authorizationRevision), desc(A.id));
1051
+ if (args.id)
1052
+ query = query.where(eq(A.id, args.id));
1053
+ if (args.beforeRevision != null)
1054
+ query = query.where(args.beforeId
1055
+ ? or(lt(A.authorizationRevision, args.beforeRevision), and(eq(A.authorizationRevision, args.beforeRevision), lt(A.id, args.beforeId)))
1056
+ : lt(A.authorizationRevision, args.beforeRevision));
1057
+ if (args.userId)
1058
+ query = query.where(eq(A.userId, args.userId));
1031
1059
  if (args.targetKind)
1032
1060
  query = query.where(eq(A.targetKind, args.targetKind));
1033
1061
  if (args.targetId)
@@ -1036,4 +1064,368 @@ export const authorizationFunctions = {
1036
1064
  },
1037
1065
  }),
1038
1066
  };
1067
+ /** Transaction-aware primitives shared by the backend and identity adapter. */
1068
+ export const USER_ACCESS_EFFECTS = [
1069
+ ...AUTHORIZATION_EFFECTS,
1070
+ 'write:user.Membership',
1071
+ 'write:user.BranchMembership',
1072
+ 'read:user.EmailReservation',
1073
+ 'write:user.EmailReservation',
1074
+ ];
1075
+ const required = (field, code) => abortAuthorization(invalid([issue(field, code)]));
1076
+ export async function assertAssignableRoles(ctx, ids) {
1077
+ if (ids.length > 100 || new Set(ids).size !== ids.length)
1078
+ required('roleIds', 'E_ROLE_SELECTION_INVALID');
1079
+ const result = [];
1080
+ for (const id of ids) {
1081
+ const R = ctx.table('user.Role');
1082
+ const row = await ctx.db.one(from(R).where(eq(R.id, id)));
1083
+ if (row?.mode !== 'managed')
1084
+ required('roleIds', 'E_ROLE_NOT_ASSIGNABLE');
1085
+ const template = ctx.manifest.permissions.roleTemplates[String(row.templateKey)];
1086
+ if (!template ||
1087
+ template.version !== Number(row.templateVersion) ||
1088
+ template.digest !== row.templateDigest)
1089
+ required('roleIds', 'E_ROLE_NOT_ASSIGNABLE');
1090
+ const G = ctx.table('user.Grant'), S = ctx.table('user.GrantSource');
1091
+ const grants = await ctx.db.all(from(G).where(eq(G.roleId, id)));
1092
+ const sources = await ctx.db.all(from(S).where(eq(S.roleId, id)));
1093
+ if (managedRoleHealthIssues(ctx.manifest, row, grants, sources).length)
1094
+ required('roleIds', 'E_ROLE_NOT_ASSIGNABLE');
1095
+ result.push(row);
1096
+ }
1097
+ return result;
1098
+ }
1099
+ async function selectionState(ctx, args, removing = false) {
1100
+ const U = ctx.table('user.User');
1101
+ const person = await ctx.db.one(from(U).where(eq(U.id, args.userId)));
1102
+ if (!person || (!removing && (!person.active || person.accessKind !== 'internal')))
1103
+ required('userId', 'user.error.userMissing');
1104
+ if (!['tenant', 'company', 'branch'].includes(args.scopeKind))
1105
+ required('scopeKind', 'E_ASSIGNMENT_SCOPE_INVALID');
1106
+ const companies = await activeCompanyMemberships(ctx, args.userId);
1107
+ const branches = await activeBranchMemberships(ctx, args.userId, companies);
1108
+ const companyId = args.scopeKind === 'tenant' ? null : args.companyId || null;
1109
+ const branchId = args.scopeKind === 'branch' ? args.branchId || null : null;
1110
+ if (companyId && !removing) {
1111
+ const C = ctx.table('company.Company');
1112
+ if (!(await ctx.db.one(from(C).where(eq(C.id, companyId), eq(C.active, true)))))
1113
+ required('companyId', 'E_ASSIGNMENT_SCOPE_INVALID');
1114
+ }
1115
+ else if (!companyId && args.scopeKind !== 'tenant' && !removing)
1116
+ required('companyId', 'E_ASSIGNMENT_SCOPE_INVALID');
1117
+ if (args.scopeKind === 'branch' && !removing) {
1118
+ const B = ctx.table('company.Branch');
1119
+ if (!branchId ||
1120
+ !(await ctx.db.one(from(B).where(eq(B.id, branchId), eq(B.companyId, companyId), eq(B.active, true)))))
1121
+ required('branchId', 'E_ASSIGNMENT_SCOPE_INVALID');
1122
+ }
1123
+ const addCompany = !!companyId && !companies.has(companyId);
1124
+ const addBranch = !!branchId && !branches.has(branchId);
1125
+ if (!removing && (addCompany || addBranch)) {
1126
+ if (!args.addMembership)
1127
+ required('addMembership', 'E_ASSIGNMENT_MEMBERSHIP_REQUIRED');
1128
+ if (!ctx.actor)
1129
+ required('actor', 'E_ACTOR_REQUIRED');
1130
+ const allowed = await effectiveFunctionKeys(ctx, ctx.actor);
1131
+ if (allowed &&
1132
+ ((addCompany && !allowed.includes('user.grantCompany')) ||
1133
+ (addBranch && !allowed.includes('user.grantBranch'))))
1134
+ required('addMembership', 'E_MEMBERSHIP_FORBIDDEN');
1135
+ }
1136
+ if (companyId && !removing)
1137
+ companies.add(companyId);
1138
+ if (branchId && !removing)
1139
+ branches.set(branchId, companyId);
1140
+ if (!companies.size && !removing)
1141
+ required('companyId', 'E_ASSIGNMENT_MEMBERSHIP_REQUIRED');
1142
+ const scope = {
1143
+ scopeKind: args.scopeKind,
1144
+ companyId,
1145
+ branchId,
1146
+ scopeKey: args.scopeKind === 'tenant'
1147
+ ? 'tenant'
1148
+ : branchId
1149
+ ? `branch:${companyId}:${branchId}`
1150
+ : `company:${companyId}`,
1151
+ };
1152
+ const A = ctx.table('user.Assignment');
1153
+ const assignments = await ctx.db.all(from(A).where(eq(A.userId, args.userId)));
1154
+ return { companies, branches, scope, assignments, addCompany, addBranch };
1155
+ }
1156
+ export async function addSelectedRoles(ctx, args) {
1157
+ await assertAssignableRoles(ctx, args.roleIds);
1158
+ const state = await selectionState(ctx, args);
1159
+ if (state.assignments.some((a) => args.roleIds.includes(String(a.roleId)) && String(a.scopeKey ?? 'tenant') === state.scope.scopeKey))
1160
+ required('roleIds', 'E_ROLE_ALREADY_ASSIGNED');
1161
+ if (state.addCompany)
1162
+ await ctx.db.insert('user.Membership', {
1163
+ id: randomUUID(),
1164
+ userId: args.userId,
1165
+ companyId: state.scope.companyId,
1166
+ });
1167
+ if (state.addBranch)
1168
+ await ctx.db.insert('user.BranchMembership', {
1169
+ id: randomUUID(),
1170
+ userId: args.userId,
1171
+ branchId: state.scope.branchId,
1172
+ });
1173
+ const added = [];
1174
+ for (const roleId of args.roleIds) {
1175
+ const row = { id: randomUUID(), userId: args.userId, roleId, ...state.scope };
1176
+ await ctx.db.insert('user.Assignment', row);
1177
+ added.push(row);
1178
+ }
1179
+ return { assignments: added, scope: state.scope };
1180
+ }
1181
+ const selectionInput = {
1182
+ userId: 'id',
1183
+ roleIds: 'json',
1184
+ scopeKind: 'text',
1185
+ companyId: 'id?',
1186
+ branchId: 'id?',
1187
+ addMembership: 'bool?',
1188
+ };
1189
+ const parseSelection = (args) => ({
1190
+ userId: String(args.userId),
1191
+ roleIds: Array.isArray(args.roleIds)
1192
+ ? args.roleIds.map(String)
1193
+ : required('roleIds', 'E_ROLE_SELECTION_INVALID'),
1194
+ scopeKind: String(args.scopeKind),
1195
+ companyId: args.companyId,
1196
+ branchId: args.branchId,
1197
+ addMembership: args.addMembership === true,
1198
+ });
1199
+ export async function checkUserEmail(ctx, email, userId) {
1200
+ const normalized = email.trim().toLowerCase();
1201
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(normalized))
1202
+ return { ok: false, available: false, code: 'invalid' };
1203
+ const U = ctx.table('user.User'), E = ctx.table('user.EmailReservation');
1204
+ const users = await ctx.db.all(from(U).where(ilike(U.email, normalized.replace(/[\\%_]/g, '\\$&'), true)));
1205
+ const held = await ctx.db.one(from(E).where(eq(E.id, normalized)));
1206
+ return { ok: true, available: !users.some((p) => p.id !== userId) && (!held || held.userId === userId) };
1207
+ }
1208
+ export async function reserveUserEmail(ctx, email, userId) {
1209
+ const normalized = email?.trim().toLowerCase() ?? '';
1210
+ const E = ctx.table('user.EmailReservation');
1211
+ if (normalized && !(await checkUserEmail(ctx, normalized, userId)).available)
1212
+ required('email', 'E_EMAIL_UNAVAILABLE');
1213
+ const previous = await ctx.db.all(from(E).where(eq(E.userId, userId)));
1214
+ if (previous.some((row) => row.id === normalized))
1215
+ return;
1216
+ await ctx.db.del(deleteFrom(E).where(eq(E.userId, userId)));
1217
+ if (normalized) {
1218
+ const held = await ctx.db.insertIfAbsent('user.EmailReservation', { id: normalized, userId });
1219
+ if (!('dryRun' in held) && !held.inserted)
1220
+ required('email', 'E_EMAIL_UNAVAILABLE');
1221
+ }
1222
+ }
1223
+ export const accessWorkflowFunctions = {
1224
+ checkUserEmail: defineFn({
1225
+ input: { email: 'text' },
1226
+ output: { ok: 'bool', available: 'bool', code: 'text?' },
1227
+ effects: ['read:user.User', 'read:user.EmailReservation'],
1228
+ handler: (ctx, args) => checkUserEmail(ctx, String(args.email)),
1229
+ }),
1230
+ assignRoles: defineFn({
1231
+ input: {
1232
+ ...selectionInput,
1233
+ reason: 'text',
1234
+ expectedAuthorizationRevision: 'int',
1235
+ idempotencyKey: 'text',
1236
+ },
1237
+ output: { ok: 'bool', revision: 'int?', errors: 'json?', replayed: 'bool?' },
1238
+ effects: USER_ACCESS_EFFECTS,
1239
+ idempotent: true,
1240
+ handler: (ctx, args) => authorizationTransaction(ctx, async (tx) => {
1241
+ const op = `role-batch:${String(args.idempotencyKey).trim()}`;
1242
+ if (!String(args.reason).trim() || op.endsWith(':'))
1243
+ required('reason', 'user.error.required');
1244
+ const replay = await operationReplay(tx, op, args);
1245
+ if ('conflict' in replay)
1246
+ required('idempotencyKey', 'E_AUTHORIZATION_REVISION_CONFLICT');
1247
+ if ('replay' in replay && replay.replay)
1248
+ return { ...replay.result, replayed: true };
1249
+ const selection = parseSelection(args);
1250
+ if (!selection.roleIds.length)
1251
+ required('roleIds', 'E_ROLE_SELECTION_INVALID');
1252
+ if ((await authorizationRevisionOf(tx)) !== args.expectedAuthorizationRevision)
1253
+ required('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT');
1254
+ const added = await addSelectedRoles(tx, selection);
1255
+ const revision = (await bumpRevision(tx, Number(args.expectedAuthorizationRevision))) ??
1256
+ required('expectedAuthorizationRevision', 'E_AUTHORIZATION_REVISION_CONFLICT');
1257
+ await recordAuthorizationAudit(tx, {
1258
+ event: 'authorization.assignment.created',
1259
+ targetKind: 'user',
1260
+ targetId: selection.userId,
1261
+ userId: selection.userId,
1262
+ scopeKey: added.scope.scopeKey,
1263
+ source: 'system-roles',
1264
+ reason: String(args.reason).trim(),
1265
+ before: null,
1266
+ after: added.assignments,
1267
+ revision,
1268
+ metadata: { roleIds: selection.roleIds, assignmentIds: added.assignments.map((a) => a.id) },
1269
+ });
1270
+ const result = { ok: true, revision };
1271
+ await completeOperation(tx, op, result);
1272
+ return result;
1273
+ }),
1274
+ }),
1275
+ previewRoleAssignment: defineFn({
1276
+ input: { ...selectionInput, assignmentId: 'id?' },
1277
+ output: { ok: 'bool', revision: 'int?', contexts: 'json?', errors: 'json?' },
1278
+ effects: AUTHORIZATION_EFFECTS.filter((x) => !x.startsWith('write:')),
1279
+ handler: (ctx, args) => authorizationTransaction(ctx, async (tx) => {
1280
+ const selection = parseSelection(args), state = await selectionState(tx, selection, !!args.assignmentId);
1281
+ let next;
1282
+ if (args.assignmentId) {
1283
+ if (!state.assignments.some((a) => a.id === args.assignmentId))
1284
+ required('assignmentId', 'user.error.roleMissing');
1285
+ next = state.assignments.filter((a) => a.id !== args.assignmentId);
1286
+ }
1287
+ else {
1288
+ await assertAssignableRoles(tx, selection.roleIds);
1289
+ if (state.assignments.some((a) => selection.roleIds.includes(String(a.roleId)) &&
1290
+ String(a.scopeKey ?? 'tenant') === state.scope.scopeKey))
1291
+ required('roleIds', 'E_ROLE_ALREADY_ASSIGNED');
1292
+ next = [
1293
+ ...state.assignments,
1294
+ ...selection.roleIds.map((roleId) => ({
1295
+ id: `preview:${roleId}`,
1296
+ userId: selection.userId,
1297
+ roleId,
1298
+ ...state.scope,
1299
+ })),
1300
+ ];
1301
+ }
1302
+ const contexts = [...state.companies]
1303
+ .filter((c) => !state.scope.companyId || c === state.scope.companyId)
1304
+ .flatMap((companyId) => [
1305
+ ...(!state.scope.branchId ? [{ companyId, branchId: null }] : []),
1306
+ ...[...state.branches]
1307
+ .filter(([b, c]) => c === companyId && (!state.scope.branchId || b === state.scope.branchId))
1308
+ .map(([branchId]) => ({ companyId, branchId })),
1309
+ ]);
1310
+ const results = [];
1311
+ for (const context of contexts) {
1312
+ const before = await resolveEffectivePermissions(tx, selection.userId, context);
1313
+ const after = await resolveEffectivePermissions(tx, selection.userId, {
1314
+ ...context,
1315
+ assignments: next,
1316
+ companies: state.companies,
1317
+ branches: state.branches,
1318
+ });
1319
+ const old = new Set(before.functions.map((f) => f.key)), fresh = new Set(after.functions.map((f) => f.key));
1320
+ const added = [...fresh].filter((k) => !old.has(k)), removed = [...old].filter((k) => !fresh.has(k));
1321
+ const bundles = Object.values(tx.manifest.permissions.bundles)
1322
+ .filter((b) => b.functions.some((k) => added.includes(k) || removed.includes(k)))
1323
+ .map((b) => ({
1324
+ key: b.key,
1325
+ labels: b.labels,
1326
+ before: b.functions.filter((k) => old.has(k)).length,
1327
+ after: b.functions.filter((k) => fresh.has(k)).length,
1328
+ total: b.functions.length,
1329
+ }));
1330
+ results.push({
1331
+ ...context,
1332
+ superuser: before.superuser,
1333
+ added,
1334
+ removed,
1335
+ bundles,
1336
+ retainedBundles: Object.values(tx.manifest.permissions.bundles)
1337
+ .filter((b) => b.functions.some((k) => old.has(k) && fresh.has(k)))
1338
+ .map((b) => ({
1339
+ key: b.key,
1340
+ labels: b.labels,
1341
+ complete: b.functions.every((k) => fresh.has(k)),
1342
+ })),
1343
+ sensitiveChange: [...added, ...removed].some((k) => ['sensitive', 'security'].includes(tx.manifest.permissions.functions[k]?.risk ?? '')),
1344
+ retained: [...old].filter((k) => fresh.has(k)),
1345
+ });
1346
+ }
1347
+ const B = tx.table('company.Branch');
1348
+ const roots = new Set((await tx.db.all(from(B).where(isNotNull(B.rootKey)))).map((b) => String(b.id)));
1349
+ const contextsWithoutDuplicateRoots = results.filter((c) => {
1350
+ if (!c.branchId || !roots.has(c.branchId))
1351
+ return true;
1352
+ const parent = results.find((p) => p.companyId === c.companyId && !p.branchId);
1353
+ return (!parent ||
1354
+ permissionDigest({
1355
+ added: parent.added,
1356
+ removed: parent.removed,
1357
+ retained: parent.retained,
1358
+ superuser: parent.superuser,
1359
+ }) !==
1360
+ permissionDigest({
1361
+ added: c.added,
1362
+ removed: c.removed,
1363
+ retained: c.retained,
1364
+ superuser: c.superuser,
1365
+ }));
1366
+ });
1367
+ return {
1368
+ ok: true,
1369
+ revision: await authorizationRevisionOf(tx),
1370
+ contexts: contextsWithoutDuplicateRoots,
1371
+ };
1372
+ }),
1373
+ }),
1374
+ };
1375
+ /** Caller owns the identity adapter; this primitive owns the atomic local account/access boundary. */
1376
+ export async function createInternalUserWithAccess(ctx, input, afterCreate) {
1377
+ return authorizationTransaction(ctx, async (tx) => {
1378
+ const name = input.name.trim(), login = input.login.normalize('NFKC').trim().toLowerCase(), email = input.email.trim().toLowerCase();
1379
+ if (!name || !login || !input.reason.trim())
1380
+ required('name', 'user.error.required');
1381
+ const U = tx.table('user.User');
1382
+ if (await tx.db.one(from(U).where(eq(U.login, login))))
1383
+ required('login', 'user.error.loginUnique');
1384
+ if (!(await checkUserEmail(tx, email)).available)
1385
+ required('email', 'E_EMAIL_UNAVAILABLE');
1386
+ const reserved = await tx.db.insertIfAbsent('user.EmailReservation', { id: email, userId: input.id });
1387
+ if (!('dryRun' in reserved) && !reserved.inserted)
1388
+ required('email', 'E_EMAIL_UNAVAILABLE');
1389
+ await tx.db.insert('user.User', {
1390
+ id: input.id,
1391
+ name,
1392
+ login,
1393
+ email,
1394
+ passwordHash: null,
1395
+ partnerId: null,
1396
+ lang: null,
1397
+ timezone: null,
1398
+ defaultCompanyId: input.companyId,
1399
+ defaultBranchId: input.branchId || null,
1400
+ accessKind: 'internal',
1401
+ securityVersion: 0,
1402
+ lastLoginAt: null,
1403
+ superuser: false,
1404
+ active: true,
1405
+ });
1406
+ const added = await addSelectedRoles(tx, {
1407
+ userId: input.id,
1408
+ roleIds: input.roleIds,
1409
+ scopeKind: input.branchId ? 'branch' : 'company',
1410
+ companyId: input.companyId,
1411
+ branchId: input.branchId,
1412
+ addMembership: true,
1413
+ });
1414
+ const revision = await advanceAuthorizationRevision(tx);
1415
+ await recordAuthorizationAudit(tx, {
1416
+ event: 'authorization.user.created',
1417
+ targetKind: 'user',
1418
+ targetId: input.id,
1419
+ userId: input.id,
1420
+ scopeKey: added.scope.scopeKey,
1421
+ source: 'system-roles',
1422
+ reason: input.reason.trim(),
1423
+ before: null,
1424
+ after: added.assignments,
1425
+ revision,
1426
+ metadata: { roleIds: input.roleIds, assignmentIds: added.assignments.map((a) => a.id) },
1427
+ });
1428
+ return { ok: true, userId: input.id, ...(await afterCreate(tx)) };
1429
+ });
1430
+ }
1039
1431
  //# sourceMappingURL=authorization.js.map