@antelopejs/interface-dms 0.10.4 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +21 -10
  3. package/dist/attachments.d.ts +15 -2
  4. package/dist/attachments.js +14 -1
  5. package/dist/attachments.js.map +1 -1
  6. package/dist/auth/db/models/sessions.model.d.ts +7 -5
  7. package/dist/auth/db/models/sessions.model.js +2 -9
  8. package/dist/auth/db/models/sessions.model.js.map +1 -1
  9. package/dist/auth/db/models/userExternalIdentities.model.d.ts +7 -5
  10. package/dist/auth/db/models/users.model.d.ts +7 -5
  11. package/dist/auth/db/tables/users.table.d.ts +2 -2
  12. package/dist/auth/db/tables/users.table.js.map +1 -1
  13. package/dist/auth/index.d.ts +4 -4
  14. package/dist/auth/internal/jwt-auth-decorator.d.ts +1 -1
  15. package/dist/auth/internal/jwt-auth-decorator.js +6 -3
  16. package/dist/auth/internal/jwt-auth-decorator.js.map +1 -1
  17. package/dist/auth/internal/request-authenticators.d.ts +12 -0
  18. package/dist/auth/internal/request-authenticators.js +53 -10
  19. package/dist/auth/internal/request-authenticators.js.map +1 -1
  20. package/dist/base/activity-feed.d.ts +34 -32
  21. package/dist/base/activity-feed.js +2 -0
  22. package/dist/base/activity-feed.js.map +1 -1
  23. package/dist/base/banner.d.ts +12 -10
  24. package/dist/base/banner.js +2 -0
  25. package/dist/base/banner.js.map +1 -1
  26. package/dist/base/block-registry/internal/describe.js +4 -12
  27. package/dist/base/block-registry/internal/describe.js.map +1 -1
  28. package/dist/base/block-registry/internal/ui.js +1 -1
  29. package/dist/base/block-registry/internal/ui.js.map +1 -1
  30. package/dist/base/card.d.ts +11 -9
  31. package/dist/base/card.js +2 -0
  32. package/dist/base/card.js.map +1 -1
  33. package/dist/base/chart-card.d.ts +4 -2
  34. package/dist/base/chart-card.js +2 -0
  35. package/dist/base/chart-card.js.map +1 -1
  36. package/dist/base/chart-schemas.d.ts +148 -148
  37. package/dist/base/chart.d.ts +65 -0
  38. package/dist/base/chart.js +65 -0
  39. package/dist/base/chart.js.map +1 -1
  40. package/dist/base/code-block.d.ts +5 -3
  41. package/dist/base/code-block.js +2 -0
  42. package/dist/base/code-block.js.map +1 -1
  43. package/dist/base/custom.d.ts +5 -0
  44. package/dist/base/custom.js +5 -0
  45. package/dist/base/custom.js.map +1 -1
  46. package/dist/base/data-types/default/address.d.ts +5 -0
  47. package/dist/base/data-types/default/address.js +5 -0
  48. package/dist/base/data-types/default/address.js.map +1 -1
  49. package/dist/base/data-types/default/choices.d.ts +20 -0
  50. package/dist/base/data-types/default/choices.js +20 -0
  51. package/dist/base/data-types/default/choices.js.map +1 -1
  52. package/dist/base/data-types/default/files.d.ts +22 -5
  53. package/dist/base/data-types/default/files.js +20 -0
  54. package/dist/base/data-types/default/files.js.map +1 -1
  55. package/dist/base/data-types/default/numbers.d.ts +15 -0
  56. package/dist/base/data-types/default/numbers.js +25 -9
  57. package/dist/base/data-types/default/numbers.js.map +1 -1
  58. package/dist/base/data-types/default/relations.d.ts +10 -2
  59. package/dist/base/data-types/default/relations.js +9 -6
  60. package/dist/base/data-types/default/relations.js.map +1 -1
  61. package/dist/base/data-types/default/strings.d.ts +25 -0
  62. package/dist/base/data-types/default/strings.js +25 -0
  63. package/dist/base/data-types/default/strings.js.map +1 -1
  64. package/dist/base/data-types/default-types.d.ts +1 -1
  65. package/dist/base/data-types/field-types.d.ts +10 -0
  66. package/dist/base/data-types/field-types.js +10 -0
  67. package/dist/base/data-types/field-types.js.map +1 -1
  68. package/dist/base/data-types/internal/relation-endpoints.js +1 -0
  69. package/dist/base/data-types/internal/relation-endpoints.js.map +1 -1
  70. package/dist/base/data-types/status-type.d.ts +5 -0
  71. package/dist/base/data-types/status-type.js +5 -0
  72. package/dist/base/data-types/status-type.js.map +1 -1
  73. package/dist/base/display.d.ts +6 -6
  74. package/dist/base/empty-state.d.ts +15 -13
  75. package/dist/base/empty-state.js +2 -0
  76. package/dist/base/empty-state.js.map +1 -1
  77. package/dist/base/form-block-schema.d.ts +50 -50
  78. package/dist/base/form-schema.d.ts +2 -0
  79. package/dist/base/form-schema.js +2 -0
  80. package/dist/base/form-schema.js.map +1 -1
  81. package/dist/base/grid.d.ts +4 -0
  82. package/dist/base/grid.js +4 -0
  83. package/dist/base/grid.js.map +1 -1
  84. package/dist/base/guard.d.ts +30 -28
  85. package/dist/base/guard.js +2 -0
  86. package/dist/base/guard.js.map +1 -1
  87. package/dist/base/helpers/archive-mode-helpers.d.ts +1 -1
  88. package/dist/base/helpers/file-refs.js +15 -15
  89. package/dist/base/helpers/file-refs.js.map +1 -1
  90. package/dist/base/internal/form-schema-validation.js +7 -1
  91. package/dist/base/internal/form-schema-validation.js.map +1 -1
  92. package/dist/base/internal/realtime-topics.js.map +1 -1
  93. package/dist/base/internal/searchable.d.ts +2 -1
  94. package/dist/base/internal/searchable.js +1 -3
  95. package/dist/base/internal/searchable.js.map +1 -1
  96. package/dist/base/key-value-list.d.ts +21 -19
  97. package/dist/base/key-value-list.js +2 -0
  98. package/dist/base/key-value-list.js.map +1 -1
  99. package/dist/base/kpi-card.d.ts +4 -2
  100. package/dist/base/kpi-card.js +2 -0
  101. package/dist/base/kpi-card.js.map +1 -1
  102. package/dist/base/live-toggle.d.ts +2 -0
  103. package/dist/base/live-toggle.js +2 -0
  104. package/dist/base/live-toggle.js.map +1 -1
  105. package/dist/base/meter.d.ts +23 -21
  106. package/dist/base/meter.js +2 -0
  107. package/dist/base/meter.js.map +1 -1
  108. package/dist/base/nav-card-grid.d.ts +24 -19
  109. package/dist/base/nav-card-grid.js +5 -0
  110. package/dist/base/nav-card-grid.js.map +1 -1
  111. package/dist/base/page-toolbar.d.ts +2 -0
  112. package/dist/base/page-toolbar.js +2 -0
  113. package/dist/base/page-toolbar.js.map +1 -1
  114. package/dist/base/period-selector.d.ts +3 -1
  115. package/dist/base/period-selector.js +2 -0
  116. package/dist/base/period-selector.js.map +1 -1
  117. package/dist/base/placeholder.d.ts +2 -0
  118. package/dist/base/placeholder.js +2 -0
  119. package/dist/base/placeholder.js.map +1 -1
  120. package/dist/base/resource-form-schema.d.ts +2 -2
  121. package/dist/base/searchable.d.ts +3 -2
  122. package/dist/base/searchable.js +2 -2
  123. package/dist/base/searchable.js.map +1 -1
  124. package/dist/base/section.d.ts +6 -2
  125. package/dist/base/section.js +4 -0
  126. package/dist/base/section.js.map +1 -1
  127. package/dist/base/stack.d.ts +6 -0
  128. package/dist/base/stack.js +6 -0
  129. package/dist/base/stack.js.map +1 -1
  130. package/dist/base/stat-group.d.ts +16 -14
  131. package/dist/base/stat-group.js +2 -0
  132. package/dist/base/stat-group.js.map +1 -1
  133. package/dist/base/tab.d.ts +10 -8
  134. package/dist/base/tab.js +2 -0
  135. package/dist/base/tab.js.map +1 -1
  136. package/dist/base/table-view/column-display.d.ts +1 -1
  137. package/dist/base/table-view/column-display.js.map +1 -1
  138. package/dist/base/table-view/data-functions.d.ts +10 -4
  139. package/dist/base/table-view/data-functions.js.map +1 -1
  140. package/dist/base/table-view/factory.d.ts +5 -0
  141. package/dist/base/table-view/factory.js +8 -4
  142. package/dist/base/table-view/factory.js.map +1 -1
  143. package/dist/base/table-view/internal/auth.d.ts +17 -3
  144. package/dist/base/table-view/internal/auth.js +26 -5
  145. package/dist/base/table-view/internal/auth.js.map +1 -1
  146. package/dist/base/table-view/internal/column-conditions.d.ts +8 -1
  147. package/dist/base/table-view/internal/column-conditions.js +25 -13
  148. package/dist/base/table-view/internal/column-conditions.js.map +1 -1
  149. package/dist/base/table-view/internal/column-display.d.ts +1 -2
  150. package/dist/base/table-view/internal/column-display.js +3 -9
  151. package/dist/base/table-view/internal/column-display.js.map +1 -1
  152. package/dist/base/table-view/internal/factory-helpers.d.ts +1 -1
  153. package/dist/base/table-view/internal/factory-helpers.js +17 -14
  154. package/dist/base/table-view/internal/factory-helpers.js.map +1 -1
  155. package/dist/base/table-view/internal/factory-lifecycle-created.d.ts +2 -3
  156. package/dist/base/table-view/internal/factory-lifecycle-created.js +5 -5
  157. package/dist/base/table-view/internal/factory-lifecycle-created.js.map +1 -1
  158. package/dist/base/table-view/internal/factory-lifecycle-filter.d.ts +2 -2
  159. package/dist/base/table-view/internal/factory-private-actions.d.ts +2 -2
  160. package/dist/base/table-view/internal/files.d.ts +2 -1
  161. package/dist/base/table-view/internal/files.js +14 -5
  162. package/dist/base/table-view/internal/files.js.map +1 -1
  163. package/dist/base/table-view/internal/footer.d.ts +3 -3
  164. package/dist/base/table-view/internal/footer.js +16 -15
  165. package/dist/base/table-view/internal/footer.js.map +1 -1
  166. package/dist/base/table-view/internal/guards.d.ts +27 -5
  167. package/dist/base/table-view/internal/guards.js +133 -49
  168. package/dist/base/table-view/internal/guards.js.map +1 -1
  169. package/dist/base/table-view/internal/meta-helpers.d.ts +1 -1
  170. package/dist/base/table-view/internal/meta-helpers.js +3 -4
  171. package/dist/base/table-view/internal/meta-helpers.js.map +1 -1
  172. package/dist/base/table-view/internal/meta.js +2 -3
  173. package/dist/base/table-view/internal/meta.js.map +1 -1
  174. package/dist/base/table-view/internal/option-contract.d.ts +9 -1
  175. package/dist/base/table-view/internal/option-footer.d.ts +2 -2
  176. package/dist/base/table-view/internal/option-row-actions.d.ts +1 -1
  177. package/dist/base/table-view/internal/realtime.d.ts +52 -11
  178. package/dist/base/table-view/internal/realtime.js +23 -16
  179. package/dist/base/table-view/internal/realtime.js.map +1 -1
  180. package/dist/base/table-view/internal/resource-form.d.ts +1 -1
  181. package/dist/base/table-view/internal/resource-form.js +1 -1
  182. package/dist/base/table-view/internal/resource-form.js.map +1 -1
  183. package/dist/base/table-view/internal/row-actions-schema.d.ts +12 -12
  184. package/dist/base/table-view/internal/row-rule-shape.d.ts +11 -5
  185. package/dist/base/table-view/internal/row-rule-shape.js +60 -30
  186. package/dist/base/table-view/internal/row-rule-shape.js.map +1 -1
  187. package/dist/base/table-view/internal/row-rules.d.ts +28 -1
  188. package/dist/base/table-view/internal/row-rules.js +8 -19
  189. package/dist/base/table-view/internal/row-rules.js.map +1 -1
  190. package/dist/base/table-view/internal/tabs.d.ts +1 -1
  191. package/dist/base/table-view/internal/tabs.js +1 -4
  192. package/dist/base/table-view/internal/tabs.js.map +1 -1
  193. package/dist/base/table-view/internal/validation.d.ts +2 -2
  194. package/dist/base/table-view/internal/validation.js +7 -7
  195. package/dist/base/table-view/internal/validation.js.map +1 -1
  196. package/dist/base/table-view/meta.d.ts +7 -6
  197. package/dist/base/table-view/meta.js +12 -7
  198. package/dist/base/table-view/meta.js.map +1 -1
  199. package/dist/base/table-view/resource-form.d.ts +3 -1
  200. package/dist/base/table-view/resource-form.js +2 -0
  201. package/dist/base/table-view/resource-form.js.map +1 -1
  202. package/dist/base/table-view/routes.d.ts +73 -21
  203. package/dist/base/table-view/routes.js +13 -10
  204. package/dist/base/table-view/routes.js.map +1 -1
  205. package/dist/base/table-view/schema.d.ts +157 -152
  206. package/dist/base/table-view/schema.js +2 -1
  207. package/dist/base/table-view/schema.js.map +1 -1
  208. package/dist/base/table-view/source.js +4 -1
  209. package/dist/base/table-view/source.js.map +1 -1
  210. package/dist/base/table-view.d.ts +1 -1
  211. package/dist/base/table-view.js.map +1 -1
  212. package/dist/base/top-list-card.d.ts +35 -33
  213. package/dist/base/top-list-card.js +2 -0
  214. package/dist/base/top-list-card.js.map +1 -1
  215. package/dist/base/types/error.js +5 -2
  216. package/dist/base/types/error.js.map +1 -1
  217. package/dist/base/types/guards.d.ts +4 -4
  218. package/dist/base/types/json.d.ts +1 -10
  219. package/dist/base/types/row-action.d.ts +7 -7
  220. package/dist/component-options/confirm-dialog.d.ts +2 -0
  221. package/dist/component-options/confirm-dialog.js.map +1 -1
  222. package/dist/component-options/data-type.d.ts +2 -2
  223. package/dist/component-options/data-type.js +7 -7
  224. package/dist/component-options/data-type.js.map +1 -1
  225. package/dist/component-options/page-layout.d.ts +3 -2
  226. package/dist/component-options/page-layout.js.map +1 -1
  227. package/dist/component-options/row-action.d.ts +14 -9
  228. package/dist/component-slots.d.ts +11 -2
  229. package/dist/component-slots.js +1 -0
  230. package/dist/component-slots.js.map +1 -1
  231. package/dist/component.d.ts +7 -8
  232. package/dist/component.js +17 -25
  233. package/dist/component.js.map +1 -1
  234. package/dist/data-controllers/members.d.ts +11 -7
  235. package/dist/data-controllers/members.js +1 -1
  236. package/dist/data-controllers/members.js.map +1 -1
  237. package/dist/data-controllers/roles.d.ts +32 -10
  238. package/dist/db/internal/exportJobs.model.d.ts +7 -5
  239. package/dist/db/internal/inviteResolutions.model.d.ts +7 -5
  240. package/dist/db/internal/tenantLifecycle.model.d.ts +15 -7
  241. package/dist/db/internal/tenantLifecycle.model.js +16 -7
  242. package/dist/db/internal/tenantLifecycle.model.js.map +1 -1
  243. package/dist/db/models/roles.model.d.ts +7 -5
  244. package/dist/db/models/tenantMembers.model.d.ts +7 -5
  245. package/dist/db/models/tenants.model.d.ts +7 -5
  246. package/dist/db/models/user_invites.model.d.ts +7 -5
  247. package/dist/guards.d.ts +1 -1
  248. package/dist/hooks.d.ts +1 -2
  249. package/dist/hooks.js +33 -26
  250. package/dist/hooks.js.map +1 -1
  251. package/dist/internal/attachments.d.ts +2 -2
  252. package/dist/internal/component-builder.js +2 -1
  253. package/dist/internal/component-builder.js.map +1 -1
  254. package/dist/internal/component-serialization.d.ts +2 -4
  255. package/dist/internal/component-serialization.js +4 -3
  256. package/dist/internal/component-serialization.js.map +1 -1
  257. package/dist/internal/component-slots.d.ts +19 -2
  258. package/dist/internal/component-slots.js +35 -12
  259. package/dist/internal/component-slots.js.map +1 -1
  260. package/dist/internal/component-watch-filter.d.ts +11 -0
  261. package/dist/internal/component-watch-filter.js +22 -0
  262. package/dist/internal/component-watch-filter.js.map +1 -0
  263. package/dist/internal/component-watches.js +0 -1
  264. package/dist/internal/component-watches.js.map +1 -1
  265. package/dist/internal/own-entry.d.ts +7 -0
  266. package/dist/internal/own-entry.js +13 -0
  267. package/dist/internal/own-entry.js.map +1 -0
  268. package/dist/internal/uploads.d.ts +30 -0
  269. package/dist/internal/uploads.js +19 -0
  270. package/dist/internal/uploads.js.map +1 -1
  271. package/dist/invite-extension-payloads.d.ts +14 -2
  272. package/dist/invite-extensions/internal/delivery.js +1 -2
  273. package/dist/invite-extensions/internal/delivery.js.map +1 -1
  274. package/dist/invite-extensions/internal/edit.d.ts +2 -1
  275. package/dist/invite-extensions/internal/edit.js +8 -9
  276. package/dist/invite-extensions/internal/edit.js.map +1 -1
  277. package/dist/invite-extensions/internal/form-slot.js +8 -15
  278. package/dist/invite-extensions/internal/form-slot.js.map +1 -1
  279. package/dist/invite-extensions/internal/payload-slices.d.ts +33 -3
  280. package/dist/invite-extensions/internal/payload-slices.js +84 -19
  281. package/dist/invite-extensions/internal/payload-slices.js.map +1 -1
  282. package/dist/invite-extensions/internal/types.d.ts +10 -1
  283. package/dist/invite-resolution.js +6 -4
  284. package/dist/invite-resolution.js.map +1 -1
  285. package/dist/json.d.ts +10 -0
  286. package/dist/json.js +3 -0
  287. package/dist/json.js.map +1 -0
  288. package/dist/layout-banners.d.ts +5 -2
  289. package/dist/layout-banners.js.map +1 -1
  290. package/dist/page/controllers.d.ts +2 -2
  291. package/dist/page/internal/extension-validation.js +3 -2
  292. package/dist/page/internal/extension-validation.js.map +1 -1
  293. package/dist/page/internal/metadata-layout.js +1 -1
  294. package/dist/page/internal/metadata-layout.js.map +1 -1
  295. package/dist/page/internal/metadata-publish.js +3 -2
  296. package/dist/page/internal/metadata-publish.js.map +1 -1
  297. package/dist/page/internal/metadata-registration.js +5 -4
  298. package/dist/page/internal/metadata-registration.js.map +1 -1
  299. package/dist/page/internal/metadata-upload-registration.d.ts +4 -2
  300. package/dist/page/internal/metadata-upload-registration.js +13 -4
  301. package/dist/page/internal/metadata-upload-registration.js.map +1 -1
  302. package/dist/page/internal/metadata-uploads.d.ts +5 -5
  303. package/dist/page/internal/metadata-uploads.js +4 -8
  304. package/dist/page/internal/metadata-uploads.js.map +1 -1
  305. package/dist/page/internal/page-record.d.ts +2 -3
  306. package/dist/page/internal/page-record.js +23 -16
  307. package/dist/page/internal/page-record.js.map +1 -1
  308. package/dist/page/layout-filter.js +17 -6
  309. package/dist/page/layout-filter.js.map +1 -1
  310. package/dist/page/metadata.js +5 -7
  311. package/dist/page/metadata.js.map +1 -1
  312. package/dist/request-tenant.d.ts +10 -4
  313. package/dist/request-tenant.js +16 -34
  314. package/dist/request-tenant.js.map +1 -1
  315. package/dist/tenant-export.d.ts +2 -1
  316. package/dist/tenant-lifecycle.d.ts +43 -4
  317. package/dist/tenant-lifecycle.js +97 -18
  318. package/dist/tenant-lifecycle.js.map +1 -1
  319. package/dist/types.d.ts +20 -0
  320. package/dist/uploads.d.ts +1 -1
  321. package/dist/uploads.js +39 -60
  322. package/dist/uploads.js.map +1 -1
  323. package/dist/utils/fire-and-forget.js.map +1 -1
  324. package/dist/utils/internal/decorated-class.d.ts +14 -0
  325. package/dist/utils/internal/decorated-class.js +37 -0
  326. package/dist/utils/internal/decorated-class.js.map +1 -0
  327. package/dist/utils/internal/type-check.d.ts +9 -0
  328. package/dist/utils/internal/type-check.js +13 -5
  329. package/dist/utils/internal/type-check.js.map +1 -1
  330. package/dist/utils/internal/value-parser.d.ts +1 -1
  331. package/dist/utils/internal/value-parser.js +22 -9
  332. package/dist/utils/internal/value-parser.js.map +1 -1
  333. package/dist/watch-actions.d.ts +1 -1
  334. package/docs/1.core/1.introduction.md +82 -77
  335. package/docs/1.core/10.layout-banners.md +57 -45
  336. package/docs/1.core/11.modules-and-dynamic-menus.md +178 -0
  337. package/docs/1.core/12.frontend-modules-and-request-helpers.md +155 -0
  338. package/docs/1.core/13.tenant-data-export.md +120 -0
  339. package/docs/1.core/2.pages-and-modules.md +140 -367
  340. package/docs/1.core/3.components.md +21 -22
  341. package/docs/1.core/4.permissions.md +92 -71
  342. package/docs/1.core/5.tenancy.md +150 -86
  343. package/docs/1.core/6.invites.md +90 -65
  344. package/docs/1.core/7.hooks.md +96 -166
  345. package/docs/1.core/8.realtime.md +44 -45
  346. package/docs/1.core/9.quick-actions.md +37 -30
  347. package/docs/2.auth/1.introduction.md +33 -35
  348. package/docs/2.auth/2.authentication.md +60 -38
  349. package/docs/2.auth/3.tokens-and-sessions.md +56 -55
  350. package/docs/2.auth/4.users.md +79 -51
  351. package/docs/2.auth/5.external-identities.md +25 -26
  352. package/docs/3.base/1.introduction.md +64 -60
  353. package/docs/3.base/10.display-blocks.md +15 -16
  354. package/docs/3.base/11.page-layouts.md +270 -0
  355. package/docs/3.base/12.table-row-actions.md +122 -0
  356. package/docs/3.base/13.table-filters-and-forms.md +104 -0
  357. package/docs/3.base/14.table-events-and-helpers.md +114 -0
  358. package/docs/3.base/15.table-sources-and-gates.md +24 -0
  359. package/docs/3.base/2.types.md +44 -41
  360. package/docs/3.base/3.forms.md +27 -28
  361. package/docs/3.base/4.tables.md +35 -368
  362. package/docs/3.base/5.charts-and-widgets.md +28 -29
  363. package/docs/3.base/6.layout.md +21 -270
  364. package/docs/3.base/8.data-types.md +39 -40
  365. package/docs/3.base/9.helpers.md +36 -38
  366. package/docs/4.notifications/1.introduction.md +22 -26
  367. package/docs/4.notifications/2.categories-and-subjects.md +58 -55
  368. package/docs/4.notifications/3.sending.md +72 -68
  369. package/docs/5.html-render/1.introduction.md +119 -81
  370. package/docs/5.html-render/2.built-in-emails.md +71 -51
  371. package/docs/index.md +18 -29
  372. package/docs/tsconfig.json +4 -0
  373. package/package.json +11 -11
  374. package/docs/images/emails-admin-invite.png +0 -0
  375. package/docs/images/emails-change-notice.png +0 -0
  376. package/docs/images/emails-change-verification.png +0 -0
  377. package/docs/images/emails-digest-logo-dark.png +0 -0
  378. package/docs/images/emails-digest-logo-light.png +0 -0
  379. package/docs/images/emails-digest.png +0 -0
  380. package/docs/images/emails-export-partial.png +0 -0
  381. package/docs/images/emails-export-ready.png +0 -0
  382. package/docs/images/emails-reset-password.png +0 -0
  383. package/docs/images/emails-two-factor.png +0 -0
  384. package/docs/images/emails-user-validation.png +0 -0
@@ -1,21 +1,20 @@
1
1
  ---
2
2
  title: "Components"
3
3
  description: "A component is a serializable description of a piece of UI, not a live object."
4
+ mode: reference
4
5
  ---
5
6
 
6
- # Components
7
-
8
7
  ## Overview
9
8
 
10
9
  A component is a serializable description of a piece of UI, not a live object. `ComponentBuilder` collects a component name, an options object, children, watches and actions; `serialize()` turns that into plain JSON-safe data; the page layout route returns the result. Nothing in this interface renders anything.
11
10
 
12
11
  Everything on this page lives in `@antelopejs/interface-dms/component`. The [Base](/docs/interfaces/base/introduction) domain ships the concrete builders (`Form`, `TableView`, `Grid`, the chart family) built on top of these types; use this page when you write a builder of your own or when you need to know what a serialized component looks like.
13
12
 
14
- ## The Serialize-Then-Render Contract
13
+ ## Serialize, then render
15
14
 
16
15
  Two shapes describe the same component on the two sides of serialization:
17
16
 
18
- ```typescript
17
+ ```typescript fragment
19
18
  interface ComponentInfo<T = unknown, C = Component<T>, ChildType = ComponentChild<T, C>> {
20
19
  componentName: string;
21
20
  options?: T;
@@ -39,7 +38,7 @@ type ComponentInfoSerialized<T = unknown> = Omit<ComponentInfo<T>, "children"> &
39
38
 
40
39
  The extra index signature on a child is the escape hatch for per-child props a container needs (a grid column span, a tab label): whatever you pass in the child metadata survives serialization next to `id`, `component` and `slot`.
41
40
 
42
- ## Build a Component
41
+ ## Build a component
43
42
 
44
43
  `ComponentBuilder<T>` is the class you instantiate with the frontend component name. Every method returns `this`, so calls chain.
45
44
 
@@ -66,14 +65,14 @@ Assign the result to a static field of a page class and `@RegisterPage()` picks
66
65
  | `mergeOptions(opts)` | Shallow-merge into the existing options. Logs a warning and does nothing when no options were set. |
67
66
  | `child(id, component, metadata?)` | Append a child with its own positional permission. `metadata` may carry `slot` plus any additional props. A duplicate `id` logs a warning. |
68
67
  | `meta(meta)` | Merge into the component metadata (`name`, `description`, `icon`). |
69
- | `onCreated(callback)` | Run `callback(pageMetadata)` when the component's page registers. |
68
+ | `onCreated(callback)` | Run `callback(page)` when the component's page registers. `page` is typed `CreatedPage` (`target`, `pageInfo.fullId`, `pageInfo.fullSlug`); at run time it is the page's `PageMetadata`. |
70
69
  | `onFilter(callback)` | Transform the options per caller, from their permission set. |
71
70
  | `watch(event, functionId, opts?)` | Declare a watch on this component. |
72
71
  | `watchOn(childId, event, functionId, opts?)` | Declare a watch spliced into a child's options at serialization time. |
73
72
  | `watchFilter(callback)` | Filter the accumulated watches per caller. |
74
73
  | `action(id, definition)` | Declare an `Action` on the component. |
75
74
  | `button(id, { permission? })` | Register a named button a quick action can press, with the permission (an action id of this component, or any `Action`) that gates it. A duplicate `id` logs a warning. The table view registers its `customButtons` that carry an `id`. |
76
- | `navBadge({ count, page? })` | Publish a count as the navigation badge of a page (`NavBadgeSource`): `count(ctx, user)` answers it for the caller on each page visit — a number, drawn neutral, or `{ count, tone }` ([Navigation badges](#navigation-badges)) — and throws to publish nothing for a caller who may not read it. `page` names the page controller the badge goes to, the component's own page by default. A table view's tab declared with `navBadge` publishes through it; a component of your own calls it directly (the DMS's notifications, security and roles pages do). A count of zero shows no badge, and a page's static `MenuOptions.badge` wins. |
75
+ | `navBadge({ count, page? })` | Publish a count as the navigation badge of a page (`NavBadgeSource`): `count(ctx, user)` answers it for the caller on each page visit with a number, drawn neutral, or `{ count, tone }` ([Navigation badges](#navigation-badges)). It throws to publish nothing for a caller who may not read it. `page` names the page controller the badge goes to, the component's own page by default. A table view's tab declared with `navBadge` publishes through it; a component of your own calls it directly (the DMS's notifications, security and roles pages do). A count of zero shows no badge, and a page's static `MenuOptions.badge` wins. |
77
76
  | `before(anchor)` | Place the component above `anchor` (a component key of the target page) when injecting it into another page. |
78
77
  | `after(anchor)` | Place it below `anchor`. |
79
78
  | `order(value)` | Break ties between components landing at the same spot. Defaults to 0. |
@@ -82,7 +81,7 @@ Assign the result to a static field of a page class and `@RegisterPage()` picks
82
81
 
83
82
  `count` answers a number, drawn as a neutral grey pill, or a `NavBadgeCount` that also gives the tone it is drawn in, with the colours of the dashboard's status pills:
84
83
 
85
- ```typescript
84
+ ```typescript fragment
86
85
  interface NavBadgeCount {
87
86
  count: number;
88
87
  tone?: Tone; // from "@antelopejs/interface-dms/base/types/tone"
@@ -116,11 +115,11 @@ interface ComponentPlacement {
116
115
  const DEFAULT_PLACEMENT: ComponentPlacement = { side: "end", order: 0 };
117
116
  ```
118
117
 
119
- `anchor` is a component key of the target page: the name of the static field it declared the component under, passed as `.before("table")`, or a dotted path to a nested position, `.before("content.tasks")` — stored as the split path. The target page is never imported, so the key is a string. A component that declares no placement gets `DEFAULT_PLACEMENT` — appended after every component of the target page, with no ordering hint. Read the current value off `component.placement`, which is `undefined` until one of the three methods is called.
118
+ `anchor` is a component key of the target page: the name of the static field it declared the component under, passed as `.before("table")`, or a dotted path to a nested position, `.before("content.tasks")`, stored as the split path. The target page is never imported, so the key is a string. A component that declares no placement gets `DEFAULT_PLACEMENT`: appended after every component of the target page, with no ordering hint. Read the current value off `component.placement`, which is `undefined` until one of the three methods is called.
120
119
 
121
120
  Lower `order` comes first, and equal orders fall back to the extension class name and then to declaration order, so the result never depends on module start order. [Pages and Modules](/docs/interfaces/core/pages-and-modules) covers the extension mechanism these describe placement for.
122
121
 
123
- ### Children and Slots
122
+ ### Children and slots
124
123
 
125
124
  ```typescript
126
125
  import { ComponentBuilder } from "@antelopejs/interface-dms/component";
@@ -134,11 +133,11 @@ const panel = new ComponentBuilder<{ title: string }>("acme-panel")
134
133
 
135
134
  The `grow: true` above is an arbitrary extra prop: it lands on the serialized child alongside `id`, `component` and `slot`.
136
135
 
137
- ## The `Component` Class
136
+ ## The `Component` class
138
137
 
139
138
  `ComponentBuilder` extends `Component<T>`, which is also usable directly when you already hold a `ComponentInfo`:
140
139
 
141
- ```typescript
140
+ ```typescript fragment
142
141
  new Component<T>(
143
142
  componentInfo: ComponentInfoPromise<T>,
144
143
  metadata: ComponentMetadata,
@@ -159,7 +158,7 @@ new Component<T>(
159
158
  | `onPageCreated` | The callback run when the owning page registers. |
160
159
  | `onFilterCallback` | The per-caller options filter, when one is installed. |
161
160
  | `placement` | The `ComponentPlacement`, or `undefined` when none was declared. |
162
- | `serialize()` | `Promise<ComponentInfoSerialized<T>>` — awaits the component info and serializes every child. |
161
+ | `serialize()` | `Promise<ComponentInfoSerialized<T>>`. Awaits the component info and serializes every child. |
163
162
  | `serializeSync()` | The same, for a component whose info is not a promise. |
164
163
 
165
164
  Both serialize methods normalize `children` to `[]` when the component has none.
@@ -168,7 +167,7 @@ Because `componentInfo` may be a promise, a builder can resolve data at registra
168
167
 
169
168
  ## Actions
170
169
 
171
- An action is a named capability of a component that carries its own permission — "add a row", "export", "restore". Declare one on the builder:
170
+ An action is a named capability of a component that carries its own permission, such as "add a row", "export" or "restore". Declare one on the builder:
172
171
 
173
172
  ```typescript
174
173
  import { ComponentBuilder } from "@antelopejs/interface-dms/component";
@@ -195,13 +194,13 @@ An `Action` instance exposes:
195
194
  | `permissionId` | The component's permission id with `.<id>` appended, or `undefined` while the component has no permission id yet. |
196
195
  | `toPermission()` | A `Permission` built from the definition, or `undefined` when `permissionId` is. |
197
196
 
198
- The component's permission id only exists once the owning page has registered, so read `permissionId` from `onCreated` or later — not while the builder is still being assembled.
197
+ The component's permission id only exists once the owning page has registered, so read `permissionId` from `onCreated` or later, not while the builder is still being assembled.
199
198
 
200
199
  An `Action` is accepted anywhere a permission is: as the `permission` option of a page ([Pages and Modules](/docs/interfaces/core/pages-and-modules)) and as the target of `AuthUserWithPermission` ([Permissions](/docs/interfaces/core/permissions)).
201
200
 
202
201
  `getPermissionIdRef` is the seam that lets `Action` resolve its component's permission id without importing the page module: it is an object with a single `get(component)` method, replaced by `dms/page` when that module loads. Consumers read `permissionId`; there is no reason to call or reassign the ref.
203
202
 
204
- ## Per-Caller Filtering
203
+ ## Per-caller filtering
205
204
 
206
205
  `onFilter` rewrites a component's options for the caller who requested the page layout. It runs during layout resolution, after the component itself passed its permission check.
207
206
 
@@ -221,13 +220,13 @@ const list = new ComponentBuilder<ListOptions>("acme-list")
221
220
  }));
222
221
  ```
223
222
 
224
- The callback receives `(permissions: Set<string>, options: T, permissionId: string, context: ComponentFilterContext)` and returns the options — synchronously or as a promise. The third argument is the component position's own permission id, including the full parent path for a child, so a filter can derive the ids of the actions it needs to test. The fourth is the request the layout is built for: its `tenantId`, its `user` (undefined when unauthenticated), and `query`, the page URL's query when the request carries one (what a header button's `availability` reads; check its values like any input).
223
+ The callback receives `(permissions: Set<string>, options: T, permissionId: string, context: ComponentFilterContext)` and returns the options, synchronously or as a promise. The third argument is the component position's own permission id, including the full parent path for a child, so a filter can derive the ids of the actions it needs to test. The fourth is the request the layout is built for: its `tenantId`, its `user` (undefined when unauthenticated), and `query`, the page URL's query when the request carries one (what a header button's `availability` reads; check its values like any input).
225
224
 
226
225
  ## Watches
227
226
 
228
227
  A watch binds an event on a component to a registered function. The backend only describes the binding.
229
228
 
230
- ```typescript
229
+ ```typescript fragment
231
230
  watch<F extends string>(event: string, functionId: F, opts?: WatchOptions<F>): this
232
231
  watchOn<F extends string>(childId: string, event: string, functionId: F, opts?: WatchOptions<F>): this
233
232
  ```
@@ -263,7 +262,7 @@ const form = Form({ title: "Profile", fields: [] }).watch(
263
262
 
264
263
  `watchFilter(callback)` narrows the watch list per caller: it receives `(permissions, watches, permissionId)` and returns the watches to keep. It runs right after `onFilter` in the same filtering pass, and the `requirePermission` narrowing is applied to whatever it returns.
265
264
 
266
- ## Component Events
265
+ ## Component events
267
266
 
268
267
  `ComponentEvents` holds the lifecycle event names:
269
268
 
@@ -276,12 +275,12 @@ Use them as the `event` argument of `watch` or `watchOn`, or as the event name i
276
275
 
277
276
  ## `RegisterComponentSlot`
278
277
 
279
- `RegisterComponentSlot(slotId: string, resolver: ComponentSlotResolver): () => void`, from `@antelopejs/interface-dms/component-slots`, claims a component slot. The resolver receives the options of each component declaring that `slotId`, once per layout request, and returns the replacement options. Returning the input unchanged contributes nothing.
278
+ `RegisterComponentSlot(slotId: string, resolver: ComponentSlotResolver): () => void`, from `@antelopejs/interface-dms/component-slots`, claims a component slot. The resolver receives the options of each form declaring that `slotId`, once per layout request, and returns the replacement options. Returning the input unchanged contributes nothing. Only a form opens a slot: a `slotId` in the options of another component, or in a form whose options carry no `fields` array, is ignored.
280
279
 
281
- `SlotOptions` is `Record<string, unknown>`; `ComponentSlotResolver` is `(options: SlotOptions) => MaybePromise<SlotOptions>`. The latest claim for an id wins. The returned release function removes the claim only if that resolver still owns it, so releasing an older claim does not remove a newer one.
280
+ `SlotOptions` is the serialized options of a form (`FormPropsSerialized` plus `watchActions`), so `options.fields` needs no cast; `ComponentSlotResolver` is `(options: SlotOptions) => MaybePromise<SlotOptions>`. The latest claim for an id wins. The returned release function removes the claim only if that resolver still owns it, so releasing an older claim does not remove a newer one.
282
281
 
283
282
  Use the returned function when the claiming module stops. [TableView form slots](/docs/components/tables#tableview-options) show how a controller exposes a slot; the controller still owns validation and persistence of contributed fields.
284
283
 
285
- ## Next Steps
284
+ ## Next steps
286
285
 
287
286
  [Permissions](/docs/interfaces/core/permissions) covers the ids these components and actions register, and the guards that check them.
@@ -1,15 +1,12 @@
1
1
  ---
2
2
  title: "Permissions"
3
- description: "Permission ids are hierarchical strings built from the page tree: a page's `fullId`, then every component id in the position's path, then the action id when present."
3
+ description: "Permission ids are dot-separated strings built from the page tree. The registry, the permission computation, the resolvers that reshape a set, and the route guards that check it."
4
+ mode: reference
4
5
  ---
5
6
 
6
- # Permissions
7
+ A permission id is a dot-separated string built from the page tree: a page's `fullId`, then every component id in the position's path, then the action id when there is one. A role holds a flat list of granted ids, and a platform owner holds the wildcard `*`. `GetUserPermissions` returns the base set a user's roles grant. `GetEffectiveUserPermissions` runs that set through the registered resolvers, and every authorization surface uses the effective set.
7
8
 
8
- ## Overview
9
-
10
- Permission ids are hierarchical strings built from the page tree: a page's `fullId`, then every component id in the position's path, then the action id when present. A role holds a flat list of granted ids; a platform owner holds the wildcard `*`. Two functions answer "what can this user do" — `GetUserPermissions` returns the base set from roles, `GetEffectiveUserPermissions` runs that set through the registered resolvers. Authorization surfaces use the effective set.
11
-
12
- The surface spans three subpaths:
9
+ The API spans three subpaths:
13
10
 
14
11
  | Subpath | Contains |
15
12
  | ------- | -------- |
@@ -17,9 +14,9 @@ The surface spans three subpaths:
17
14
  | `@antelopejs/interface-dms/permissions-resolver` | Registering and running resolvers |
18
15
  | `@antelopejs/interface-dms/guards` | Route guards |
19
16
 
20
- ## Register a Permission
17
+ ## Register a permission
21
18
 
22
- Pages, components and actions register their own permissions when the page registers ([Pages and Modules](/docs/interfaces/core/pages-and-modules)). Register a standalone one when a capability has no component behind it:
19
+ Pages, components and actions register their own permissions when the page registers ([Pages and modules](/docs/interfaces/core/pages-and-modules)). Register a standalone permission for a capability that has no component behind it:
23
20
 
24
21
  ```typescript
25
22
  import { RegisterPermission } from "@antelopejs/interface-dms/permissions";
@@ -34,6 +31,8 @@ RegisterPermission("reports.export", {
34
31
  });
35
32
  ```
36
33
 
34
+ `RegisterPermission(id, permission)` takes the id twice, once as the registry key and once inside the record. Keep the two equal.
35
+
37
36
  ### `Permission`
38
37
 
39
38
  | Field | Type | Description |
@@ -42,16 +41,14 @@ RegisterPermission("reports.export", {
42
41
  | `title` | `string` | Label shown in the roles editor. Required. |
43
42
  | `icon` | `string` | Icon name. |
44
43
  | `description` | `string` | Longer description. |
45
- | `dependencies` | `string[]` | Permission ids this one needs to be of any use. The roles editor grants them with it (and takes it away with them), and a role is saved with them and the ids they sit under. `HasPermission` requires them, like the ids above: a registered dependency the set lacks makes the permission count for nothing. An unregistered one counts as held. |
46
- | `defaultGranted` | `boolean` | Treat the permission as held by every caller, without a role grant. |
47
-
48
- A table view's actions declare their `dependencies` themselves: `edit` and `export` need `view`, and every action only reached from the rows — `delete`, `archive`, `viewArchived`, `export`, and `add`/`edit`/`view` when their form opens over the table — needs `list`; `restore` needs `viewArchived`. A form opening as its own page and `select` need no `list`.
44
+ | `dependencies` | `string[]` | Permission ids this one needs to be of any use. The roles editor grants them with it and removes it with them, and a role is saved with them and the ids they sit under. `HasPermission` requires each registered dependency, so a missing one makes the permission count for nothing. An unregistered dependency counts as held. |
45
+ | `defaultGranted` | `boolean` | Treats the permission as held by every caller, without a role grant. |
49
46
 
50
- `RegisterPermission(id, permission)` takes the id twice — once as the registry key, once inside the record — so keep them equal.
47
+ A table view's actions declare their own `dependencies`. `edit` and `export` need `view`. Every action reached only from the rows needs `list`: `delete`, `archive`, `viewArchived`, `export`, and `add`, `edit` and `view` when their form opens over the table. `restore` needs `viewArchived`. A form that opens as its own page, and `select`, need no `list`.
51
48
 
52
- `UnregisterPermission(id)` drops a permission from the grantable tree again. It exists for registrations undone while the process keeps running — a page extension whose module stopped, for instance. A permission left behind after its surface is gone stays offered in the roles editor and stays grantable, so anything that registers a permission conditionally should unregister it on the same condition.
49
+ `UnregisterPermission(id)` drops a permission from the grantable tree. It serves registrations undone while the process keeps running, such as a page extension whose module stopped. A permission left behind after its surface is gone stays offered in the roles editor and stays grantable, so code that registers a permission under a condition unregisters it under the same condition.
53
50
 
54
- ## Read the Registry
51
+ ## Read the registry
55
52
 
56
53
  ```typescript
57
54
  import {
@@ -63,25 +60,26 @@ const permission = await GetPermission("reports.export");
63
60
  const tree = await GetPermissions();
64
61
  ```
65
62
 
66
- `GetPermission(id)` resolves to the `Permission` or `undefined`. `GetPermissions()` resolves to a `Record<string, PermissionTree>` keyed by the first id segment, where
63
+ `GetPermission(id)` resolves to the `Permission` or `undefined`. `GetPermissions()` resolves to a `Record<string, PermissionTree>` keyed by the first id segment:
67
64
 
68
- ```typescript
65
+ ```typescript fragment
69
66
  type PermissionTree = {
70
67
  data?: Permission;
71
68
  children: Record<string, PermissionTree>;
72
69
  };
73
70
  ```
74
71
 
75
- Each level keys its children by the next dot-separated segment. `data` is absent for a level that exists only to hold children.
72
+ Each level keys its children by the next dot-separated segment. `data` is absent on a level that exists only to hold children.
76
73
 
77
- Both are interface functions and return promises even though the registry is in-process.
74
+ Both are interface functions, so they return promises even though the registry is in the same process.
78
75
 
79
- ## Compute a User's Permissions
76
+ ## Compute a user's permissions
80
77
 
81
78
  ```typescript
82
79
  import {
83
80
  GetEffectiveUserPermissions,
84
81
  GetUserPermissions,
82
+ HasAnyPermission,
85
83
  HasPermission,
86
84
  } from "@antelopejs/interface-dms/permissions";
87
85
  ```
@@ -89,22 +87,26 @@ import {
89
87
  | Function | Signature |
90
88
  | -------- | --------- |
91
89
  | `GetUserPermissions` | `(user: { owner?: boolean }, roleIds: string[], roleModel: RoleModel) => Promise<Set<string>>` |
92
- | `GetEffectiveUserPermissions` | `(user: { _id: string; owner?: boolean }, tenantId: string, roleIds: string[], roleModel: RoleModel) => Promise<Set<string>>` |
90
+ | `GetEffectiveUserPermissions` | `(user: { _id: string; owner?: boolean }, tenantId: string, roleIds: string[], roleModel: RoleModel, context?: PermissionsResolverContext) => Promise<Set<string>>` |
93
91
  | `HasPermission` | `(permissions: Set<string>, permissionId: string) => Promise<boolean>` |
92
+ | `HasAnyPermission` | `(permissions: Set<string>, permissionIds: string[]) => Promise<boolean>` |
94
93
 
95
- `GetUserPermissions` returns `Set(["*"])` for a user with `owner: true`, an empty set when `roleIds` is empty, and otherwise the union of the `permissions` arrays of the roles it loads through `roleModel`. This is the **base** set: it does not account for resolvers, so do not authorize against it.
94
+ `GetUserPermissions` returns `Set(["*"])` for a user with `owner: true`, an empty set when `roleIds` is empty, and otherwise the union of the `permissions` arrays of the roles it loads through `roleModel`. That is the base set. It ignores resolvers, so never authorize against it.
96
95
 
97
- `GetEffectiveUserPermissions` composes that base set with `ApplyPermissionsResolvers` and is the set every authorization surface uses.
96
+ `GetEffectiveUserPermissions` passes the base set through `ApplyPermissionsResolvers`, with `context` (default `{}`) handed to each resolver. Every authorization surface uses this set: route guards, page and menu visibility, table view action checks and search.
98
97
 
99
- `HasPermission` resolves in this order:
98
+ `HasPermission` checks in this order:
100
99
 
101
100
  1. `true` when the set holds the wildcard `*`.
102
101
  2. `false` when the id is module-scoped.
103
102
  3. `true` when the registered permission carries `defaultGranted`.
104
103
  4. `false` when the set does not hold the id.
105
- 5. `true` when the set also holds every id the permission sits under (`pages.sales` and `pages` for `pages.sales.orders`), `false` otherwise. An id above it that is not a registered permission, or that carries `defaultGranted`, counts as held.
104
+ 5. `false` when the set lacks an id the permission sits under (`pages.sales` and `pages` for `pages.sales.orders`). An id above it that is not a registered permission, or that carries `defaultGranted`, counts as held.
105
+ 6. `false` when a registered dependency fails the same check. Otherwise `true`.
106
+
107
+ The wildcard comes first, so a platform owner passes even for a module-scoped id. Step 5 makes a grant worthless without its parents: a role that holds neither a table nor its page is refused the table's `delete` action. The roles editor and the role routes store each grant with every id above it, so only an older saved role, or a set a resolver reshaped, can miss one.
106
108
 
107
- The wildcard is checked first, so a platform owner passes even for a module-scoped id. Step 5 makes a grant worth nothing without its parents: a table's `delete` action is refused to a role that holds neither the table nor its page. The roles editor and the role routes store a grant with every id above it, so only a role saved before they did, or a set a resolver reshaped, can miss one.
109
+ `HasAnyPermission` returns `true` when `HasPermission` passes for at least one of the ids.
108
110
 
109
111
  ```typescript
110
112
  import { GetModel } from "@antelopejs/interface-database-decorators";
@@ -130,11 +132,11 @@ async function canExport(user: User, tenantId: string): Promise<boolean> {
130
132
  }
131
133
  ```
132
134
 
133
- `RoleModel` and `TenantMemberModel` come from [Tenancy](/docs/interfaces/core/tenancy).
135
+ `RoleModel` and `TenantMemberModel` are described in [Tenancy](/docs/interfaces/core/tenancy).
134
136
 
135
- ## Module-Scoped Permissions
137
+ ## Module-scoped permissions
136
138
 
137
- Permissions belonging to a module page or category are never added to the grantable permission tree, and a role grant on such an id never grants anything. They are marked instead:
139
+ The permissions of a module page or category never enter the grantable permission tree, and a role grant on such an id grants nothing. The ids are marked instead:
138
140
 
139
141
  ```typescript
140
142
  import {
@@ -144,19 +146,19 @@ import {
144
146
  } from "@antelopejs/interface-dms/permissions";
145
147
 
146
148
  MarkModuleScopedPermission("modules.automation.builder");
147
- IsModuleScopedPermission("modules.automation.builder.canvas.add"); // true
149
+ const scoped = IsModuleScopedPermission("modules.automation.builder.canvas.add");
148
150
  UnmarkModuleScopedPermission("modules.automation.builder");
149
151
  ```
150
152
 
151
- `IsModuleScopedPermission(id)` walks the id upward one dot-separated segment at a time, so marking a page id also covers every component and action id derived from it. `UnmarkModuleScopedPermission(id)` removes the mark for that exact id only.
153
+ `IsModuleScopedPermission(id)` checks the id and each id above it, one dot-separated segment at a time, so marking a page id also covers every component and action id derived from it. In the example, `scoped` is `true`. `UnmarkModuleScopedPermission(id)` removes the mark for that exact id only.
152
154
 
153
- Page registration calls `MarkModuleScopedPermission` for you whenever `isInsideModule` holds ([Pages and Modules](/docs/interfaces/core/pages-and-modules)); call it directly only for ids you build outside that path.
155
+ Page and category registration calls `MarkModuleScopedPermission` whenever `isInsideModule` holds ([Modules and dynamic menus](/docs/interfaces/core/modules-and-dynamic-menus#module-urls-and-ids)). Call it directly only for ids you build outside that path.
154
156
 
155
- ## Reshape the Effective Set
157
+ ## Reshape the effective set
156
158
 
157
159
  A permissions resolver receives a caller's current permission set for a tenant and returns the set to use instead.
158
160
 
159
- ```typescript
161
+ ```typescript fragment
160
162
  import { RegisterPermissionsResolver } from "@antelopejs/interface-dms/permissions-resolver";
161
163
 
162
164
  export async function construct(): Promise<void> {
@@ -180,23 +182,33 @@ type PermissionsResolverFn = (
180
182
  ) => Promise<Set<string>> | Set<string>;
181
183
 
182
184
  interface PermissionsResolverContext {
183
- /** Set when the call previews a role rather than computing a member's set. */
184
185
  preview?: PermissionsResolverPreview;
185
186
  }
186
187
 
187
188
  interface PermissionsResolverPreview {
188
- /** The previewed role's permissions, before any resolver reshaped them. */
189
189
  rolePermissions: ReadonlySet<string>;
190
190
  }
191
191
  ```
192
192
 
193
- `PermissionsResolverInfo` carries `id`, `resolver` and `order`.
193
+ `PermissionsResolverInfo` carries `id`, `resolver` and `order`. `context.preview` is set when the call previews a role instead of computing a member's set, and `rolePermissions` holds the previewed role's permissions before any resolver reshaped them.
194
+
195
+ The interface guarantees these semantics:
196
+
197
+ - Resolvers run in ascending `order`, each one receiving the previous one's result. Equal `order` runs in registration order, which is module load order.
198
+ - Registering an id twice replaces the earlier resolver in its slot instead of adding a second one.
199
+ - A resolver that throws aborts the computation and fails the request. It is never skipped.
200
+ - The registration is removed when the registering module unloads. There is no unregister call.
201
+ - Register in `construct()`, so the resolver is active before the API serves requests.
202
+
203
+ `ApplyPermissionsResolvers(userId, tenantId, basePermissions, context)` runs the pipeline, hands `context` to every resolver, and resolves to the effective set. `GetEffectiveUserPermissions` computes the base set and calls it, so prefer that function.
204
+
205
+ A resolver shapes a permission set. It cannot deny a surface that consults no permission set, and it cannot revoke a `defaultGranted` permission, which `HasPermission` grants after the resolvers ran. To cut a tenant off from the product entirely, register a tenant access gate instead ([Tenancy](/docs/interfaces/core/tenancy)).
194
206
 
195
207
  ### Role previews
196
208
 
197
- The roles editor's **Preview as role** runs the resolvers too, over the previewed role's set, and tells them so with `context.preview`. The `userId` of such a call is the person previewing. A resolver that grants or withholds by the user's standing (an owner handed every permission of the plan, a screen kept for owners) must check the flag, and shape the set as for a member holding only that role:
209
+ **Preview as role** in the roles editor runs the resolvers too, over the previewed role's set, and sets `context.preview`. The `userId` of such a call is the person previewing. A resolver that grants or withholds by the user's standing, such as handing an owner every permission of the plan or keeping a screen for owners, checks the flag and shapes the set as for an ordinary member holding only that role:
198
210
 
199
- ```typescript
211
+ ```typescript fragment
200
212
  RegisterPermissionsResolver({
201
213
  id: "my_app.plan-limits",
202
214
  order: 100,
@@ -210,29 +222,17 @@ RegisterPermissionsResolver({
210
222
  });
211
223
  ```
212
224
 
213
- The DMS always passes `context`: `{}` outside a preview.
214
-
215
- Semantics the interface guarantees:
225
+ The DMS always passes `context`, and passes `{}` outside a preview.
216
226
 
217
- - Resolvers run in ascending `order`, each seeing the previous one's result; equal `order` runs in registration order, which is module load order.
218
- - Registering an id twice replaces the earlier resolver rather than adding a second one.
219
- - A resolver that throws aborts the computation and fails the request. It is never skipped.
220
- - The registration is removed when the registering module unloads; there is no unregister call.
221
- - Register in `construct()` so the resolver is active before the API serves requests.
222
-
223
- `ApplyPermissionsResolvers(userId, tenantId, basePermissions, context?)` runs the pipeline, handing `context` (`{}` when omitted) to every resolver, and resolves to the effective set. Prefer `GetEffectiveUserPermissions`, which computes the base set for you and calls it.
224
-
225
- A resolver shapes a permission set. It cannot deny a surface that consults no permission set, and it cannot revoke a `defaultGranted` permission, which `HasPermission` grants after the resolvers ran. To cut a tenant off from the product entirely, register a tenant access gate instead ([Tenancy](/docs/interfaces/core/tenancy)).
226
-
227
- ## Guard a Route
227
+ ## Guard a route
228
228
 
229
229
  Three decorators from `@antelopejs/interface-dms/guards` gate a route on the caller.
230
230
 
231
231
  ### `AuthUserWithPermission`
232
232
 
233
- `AuthUserWithPermission(target, options?)` requires an authenticated user holding the exact registered permission of `target`, which is a `Component`, a page controller class, or an `Action` ([Components](/docs/interfaces/core/components)). A child component resolves to its full positional path. `options` is the same `TenantGuardOptions` the two tenant guards take, described below.
233
+ `AuthUserWithPermission(target, options?)` requires an authenticated user who holds the registered permission of `target`. `target` is a `Component`, a child position, a page controller class, or an `Action` ([Components](/docs/interfaces/core/components)). A child component resolves to its full positional path. `options` is a `TenantGuardOptions`.
234
234
 
235
- ```typescript
235
+ ```typescript fragment
236
236
  import { Controller, Get } from "@antelopejs/interface-api";
237
237
  import { AuthUserWithPermission } from "@antelopejs/interface-dms/guards";
238
238
  import type { User } from "@antelopejs/interface-dms/auth/db";
@@ -240,17 +240,25 @@ import { InvoicesPage } from "../pages/invoices";
240
240
 
241
241
  export class InvoiceReportController extends Controller("/api/invoices") {
242
242
  @Get("/summary")
243
- async summary(@AuthUserWithPermission(InvoicesPage) _user: User) {
244
- // reachable only by a caller holding the InvoicesPage permission
243
+ async summary(@AuthUserWithPermission(InvoicesPage) user: User) {
244
+ return { requestedBy: user._id };
245
245
  }
246
246
  }
247
247
  ```
248
248
 
249
- The guard resolves the target's permission id, loads the caller's membership in the tenant carried by the token, computes their effective permissions and calls `HasPermission`. It answers `403` when the permission is missing, and also when the token carries no tenant claim — a permission can only be granted inside a tenant. A target that carries no permission id yet passes the authentication check only. The tenant access gate runs whenever the token carries a tenant claim, including for a target with no permission id, and it runs before the permission is resolved — unless `options.bypassTenantAccessGate` is set, which skips the gate while leaving the permission check in place.
249
+ The guard runs these steps:
250
+
251
+ 1. It authenticates the token. A request without a valid user fails authentication.
252
+ 2. When the token carries a tenant claim, it runs the tenant access gate, unless `options.bypassTenantAccessGate` is set.
253
+ 3. It resolves the target's permission id. A target with no permission id passes with the authenticated user.
254
+ 4. When the token carries no tenant claim, it answers `403`, because a permission is granted only inside a tenant.
255
+ 5. It loads the caller's membership in that tenant, computes the effective permissions and calls `HasPermission`. A missing permission answers `403` with `Forbidden: missing permission <id>`.
256
+
257
+ `bypassTenantAccessGate` skips step 2 and keeps the permission check.
250
258
 
251
259
  ### `AuthTenantOwner` and `AuthTenantMember`
252
260
 
253
- Both resolve the tenant from the request's JWT `tenantId` claim and check the caller's membership in it. `AuthTenantOwner` additionally requires `isTenantOwner`. Each answers `401` without a token and `403` when the requirement is not met, and each runs the tenant access gate before returning the user.
261
+ `AuthTenantMember(options?)` requires a member of the request's tenant. `AuthTenantOwner(options?)` also requires `isTenantOwner` on that membership. The tenant comes from the verified credential: the JWT's `tenantId` claim, or `DEFAULT_TENANT_ID` (`"default"`) when the claim is empty. Each guard answers `401` without a valid token and `403` when the requirement fails, with the body `error.tenant_member_required` or `error.tenant_owner_required`. Each then runs the tenant access gate before it returns the user.
254
262
 
255
263
  ```typescript
256
264
  import { Controller, Post } from "@antelopejs/interface-api";
@@ -265,29 +273,42 @@ export class BillingController extends Controller("/api/billing") {
265
273
  async portalSession(
266
274
  @AuthTenantOwner({ bypassTenantAccessGate: true }) user: User,
267
275
  ) {
268
- // stays reachable for a tenant a gate denies
276
+ return { owner: user._id };
269
277
  }
270
278
 
271
279
  @Post("/usage")
272
280
  async usage(@AuthTenantMember() user: User) {
273
- // any member of the request's tenant
281
+ return { member: user._id };
274
282
  }
275
283
  }
276
284
  ```
277
285
 
286
+ The portal route stays reachable for a tenant that a gate denies. The usage route admits any member of the request's tenant.
287
+
278
288
  `TenantGuardOptions` has one field, and all three guards accept it:
279
289
 
280
290
  | Option | Type | Description |
281
291
  | ------ | ---- | ----------- |
282
- | `bypassTenantAccessGate` | `boolean` | Skip the tenant access gate for this route. Reserved for routes that must stay reachable when a gate denies the tenant. |
292
+ | `bypassTenantAccessGate` | `boolean` | Skips the tenant access gate for this route. Reserved for routes that must stay reachable when a gate denies the tenant, such as billing status, a payment portal or invoices. |
293
+
294
+ The two tenant guards accept `TenantRequestGuardOptions`, which extends it:
295
+
296
+ | Option | Type | Description |
297
+ | ------ | ---- | ----------- |
298
+ | `authenticators` | `readonly RequestAuthenticator[]` | Credential handlers this route accepts before the JWT fallback. The handler whose `recognizes(token)` returns `true` authenticates the request. Two handlers that recognize the same token answer `401`. |
299
+ | `requireOwner` | `boolean` | Requires tenant ownership, not platform ownership. `AuthTenantOwner` sets it. |
300
+
301
+ `RequestAuthenticator` comes from `@antelopejs/interface-dms/auth` ([Authentication](/docs/interfaces/auth/introduction)).
302
+
303
+ `authenticateTenantRequest(ctx, options?)` runs the same check as a function and resolves to the `User`. It authenticates the credential again, and checks membership and the tenant gate, on every call, so a long-lived request can call it repeatedly. Product permissions stay the caller's job.
283
304
 
284
- The opt-out has two mirrors elsewhere, for the surfaces a route guard does not cover: `MenuOptions.bypassTenantAccessGate` keeps a page and its menu entry reachable ([Pages and Modules](/docs/interfaces/core/pages-and-modules)), and `TableViewOptions.bypassTenantAccessGate` covers a table view's data routes, which belong to a controller rather than to any one page.
305
+ The opt-out has two mirrors for the surfaces a route guard does not cover. `MenuOptions.bypassTenantAccessGate` keeps a page and its menu entry reachable ([Pages and modules](/docs/interfaces/core/pages-and-modules#reach-a-page-behind-a-tenant-gate)). `TableViewOptions.bypassTenantAccessGate` covers a table view's data routes, which belong to a controller and not to any one page.
285
306
 
286
- A denial from a gate surfaces as a `403` whose body is the gate's `code` ([Tenancy](/docs/interfaces/core/tenancy)).
307
+ A denial from a gate answers `403` with the gate's `code` as its body ([Tenancy](/docs/interfaces/core/tenancy)).
287
308
 
288
- ### Guards as Properties
309
+ ### Guards as properties
289
310
 
290
- All three are parameter *and* property decorators. Declared as a class property, a guard gates every route on that controller:
311
+ All three guards are parameter and property decorators. Declared as a class property, a guard gates every route on that controller:
291
312
 
292
313
  ```typescript
293
314
  import { Controller, Post } from "@antelopejs/interface-api";
@@ -299,14 +320,14 @@ export class FilesController extends Controller("/api/files") {
299
320
  declare user: User;
300
321
 
301
322
  @Post("/presign")
302
- async presign() {
303
- // the guard already ran, whether or not this handler reads `this.user`
323
+ async presign(): Promise<{ ok: true }> {
324
+ return { ok: true };
304
325
  }
305
326
  }
306
327
  ```
307
328
 
308
- A property provider runs on every request to a route of the controller, independently of whether the handler body reads the property. That is what makes the property form a guard rather than a convenience: removing the read does not remove the check.
329
+ The property provider runs on every request to a route of the controller, whether or not the handler reads the property. The `presign` route above never reads `this.user`, and the guard still runs. That makes the property form a guard: removing the read does not remove the check.
309
330
 
310
- ## Next Steps
331
+ ## Next steps
311
332
 
312
333
  [Tenancy](/docs/interfaces/core/tenancy) covers the tenant the guards resolve, the roles the permission sets come from, and the access gates.