@antelopejs/interface-dms 0.10.3 → 0.12.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 (377) hide show
  1. package/CHANGELOG.md +47 -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 +4 -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 +3 -9
  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 +127 -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 +1 -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 +152 -152
  206. package/dist/base/table-view/source.js +4 -1
  207. package/dist/base/table-view/source.js.map +1 -1
  208. package/dist/base/table-view.d.ts +1 -1
  209. package/dist/base/table-view.js.map +1 -1
  210. package/dist/base/top-list-card.d.ts +35 -33
  211. package/dist/base/top-list-card.js +2 -0
  212. package/dist/base/top-list-card.js.map +1 -1
  213. package/dist/base/types/error.js +5 -2
  214. package/dist/base/types/error.js.map +1 -1
  215. package/dist/base/types/guards.d.ts +4 -4
  216. package/dist/base/types/json.d.ts +1 -10
  217. package/dist/base/types/row-action.d.ts +7 -7
  218. package/dist/component-options/confirm-dialog.d.ts +2 -0
  219. package/dist/component-options/confirm-dialog.js.map +1 -1
  220. package/dist/component-options/data-type.d.ts +2 -2
  221. package/dist/component-options/data-type.js +7 -7
  222. package/dist/component-options/data-type.js.map +1 -1
  223. package/dist/component-options/row-action.d.ts +14 -9
  224. package/dist/component-slots.d.ts +11 -2
  225. package/dist/component-slots.js +1 -0
  226. package/dist/component-slots.js.map +1 -1
  227. package/dist/component.d.ts +7 -8
  228. package/dist/component.js +17 -25
  229. package/dist/component.js.map +1 -1
  230. package/dist/data-controllers/members.d.ts +11 -7
  231. package/dist/data-controllers/members.js +1 -1
  232. package/dist/data-controllers/members.js.map +1 -1
  233. package/dist/data-controllers/roles.d.ts +32 -10
  234. package/dist/db/internal/exportJobs.model.d.ts +7 -5
  235. package/dist/db/internal/inviteResolutions.model.d.ts +7 -5
  236. package/dist/db/internal/tenantLifecycle.model.d.ts +7 -5
  237. package/dist/db/models/roles.model.d.ts +7 -5
  238. package/dist/db/models/tenantMembers.model.d.ts +7 -5
  239. package/dist/db/models/tenants.model.d.ts +7 -5
  240. package/dist/db/models/user_invites.model.d.ts +7 -5
  241. package/dist/db/tables/tenants.table.d.ts +9 -0
  242. package/dist/db/tables/tenants.table.js +4 -0
  243. package/dist/db/tables/tenants.table.js.map +1 -1
  244. package/dist/guards.d.ts +1 -1
  245. package/dist/hooks.d.ts +10 -9
  246. package/dist/hooks.js +42 -33
  247. package/dist/hooks.js.map +1 -1
  248. package/dist/internal/attachments.d.ts +2 -2
  249. package/dist/internal/component-builder.js +2 -1
  250. package/dist/internal/component-builder.js.map +1 -1
  251. package/dist/internal/component-serialization.d.ts +2 -4
  252. package/dist/internal/component-serialization.js +4 -3
  253. package/dist/internal/component-serialization.js.map +1 -1
  254. package/dist/internal/component-slots.d.ts +19 -2
  255. package/dist/internal/component-slots.js +35 -12
  256. package/dist/internal/component-slots.js.map +1 -1
  257. package/dist/internal/component-watch-filter.d.ts +11 -0
  258. package/dist/internal/component-watch-filter.js +22 -0
  259. package/dist/internal/component-watch-filter.js.map +1 -0
  260. package/dist/internal/component-watches.js +0 -1
  261. package/dist/internal/component-watches.js.map +1 -1
  262. package/dist/internal/own-entry.d.ts +7 -0
  263. package/dist/internal/own-entry.js +13 -0
  264. package/dist/internal/own-entry.js.map +1 -0
  265. package/dist/invite-extension-payloads.d.ts +14 -2
  266. package/dist/invite-extensions/internal/delivery.js +1 -2
  267. package/dist/invite-extensions/internal/delivery.js.map +1 -1
  268. package/dist/invite-extensions/internal/edit.d.ts +2 -1
  269. package/dist/invite-extensions/internal/edit.js +8 -9
  270. package/dist/invite-extensions/internal/edit.js.map +1 -1
  271. package/dist/invite-extensions/internal/form-slot.js +8 -15
  272. package/dist/invite-extensions/internal/form-slot.js.map +1 -1
  273. package/dist/invite-extensions/internal/payload-slices.d.ts +33 -3
  274. package/dist/invite-extensions/internal/payload-slices.js +84 -19
  275. package/dist/invite-extensions/internal/payload-slices.js.map +1 -1
  276. package/dist/invite-extensions/internal/types.d.ts +10 -1
  277. package/dist/invite-resolution.js +6 -4
  278. package/dist/invite-resolution.js.map +1 -1
  279. package/dist/json.d.ts +10 -0
  280. package/dist/json.js +3 -0
  281. package/dist/json.js.map +1 -0
  282. package/dist/layout-banners.d.ts +5 -2
  283. package/dist/layout-banners.js.map +1 -1
  284. package/dist/page/controllers.d.ts +2 -2
  285. package/dist/page/internal/extension-validation.js +3 -2
  286. package/dist/page/internal/extension-validation.js.map +1 -1
  287. package/dist/page/internal/metadata-layout.js +1 -1
  288. package/dist/page/internal/metadata-layout.js.map +1 -1
  289. package/dist/page/internal/metadata-publish.js +3 -2
  290. package/dist/page/internal/metadata-publish.js.map +1 -1
  291. package/dist/page/internal/metadata-registration.js +5 -4
  292. package/dist/page/internal/metadata-registration.js.map +1 -1
  293. package/dist/page/internal/metadata-upload-registration.d.ts +3 -1
  294. package/dist/page/internal/metadata-upload-registration.js +9 -0
  295. package/dist/page/internal/metadata-upload-registration.js.map +1 -1
  296. package/dist/page/internal/metadata-uploads.d.ts +18 -5
  297. package/dist/page/internal/metadata-uploads.js +7 -8
  298. package/dist/page/internal/metadata-uploads.js.map +1 -1
  299. package/dist/page/internal/page-record.d.ts +2 -3
  300. package/dist/page/internal/page-record.js +19 -14
  301. package/dist/page/internal/page-record.js.map +1 -1
  302. package/dist/page/layout-filter.js +17 -6
  303. package/dist/page/layout-filter.js.map +1 -1
  304. package/dist/page/metadata.js +5 -7
  305. package/dist/page/metadata.js.map +1 -1
  306. package/dist/request-tenant.d.ts +10 -4
  307. package/dist/request-tenant.js +16 -34
  308. package/dist/request-tenant.js.map +1 -1
  309. package/dist/tenant-export.d.ts +2 -1
  310. package/dist/tenant-lifecycle.d.ts +13 -4
  311. package/dist/tenant-lifecycle.js +51 -18
  312. package/dist/tenant-lifecycle.js.map +1 -1
  313. package/dist/types.d.ts +20 -0
  314. package/dist/uploads.js +42 -59
  315. package/dist/uploads.js.map +1 -1
  316. package/dist/utils/fire-and-forget.js.map +1 -1
  317. package/dist/utils/internal/decorated-class.d.ts +14 -0
  318. package/dist/utils/internal/decorated-class.js +37 -0
  319. package/dist/utils/internal/decorated-class.js.map +1 -0
  320. package/dist/utils/internal/type-check.d.ts +9 -0
  321. package/dist/utils/internal/type-check.js +13 -5
  322. package/dist/utils/internal/type-check.js.map +1 -1
  323. package/dist/utils/internal/value-parser.d.ts +1 -1
  324. package/dist/utils/internal/value-parser.js +22 -9
  325. package/dist/utils/internal/value-parser.js.map +1 -1
  326. package/dist/watch-actions.d.ts +1 -1
  327. package/docs/1.core/1.introduction.md +82 -77
  328. package/docs/1.core/10.layout-banners.md +57 -45
  329. package/docs/1.core/11.modules-and-dynamic-menus.md +178 -0
  330. package/docs/1.core/12.frontend-modules-and-request-helpers.md +155 -0
  331. package/docs/1.core/13.tenant-data-export.md +120 -0
  332. package/docs/1.core/2.pages-and-modules.md +140 -367
  333. package/docs/1.core/3.components.md +21 -22
  334. package/docs/1.core/4.permissions.md +92 -71
  335. package/docs/1.core/5.tenancy.md +112 -86
  336. package/docs/1.core/6.invites.md +90 -65
  337. package/docs/1.core/7.hooks.md +96 -166
  338. package/docs/1.core/8.realtime.md +44 -45
  339. package/docs/1.core/9.quick-actions.md +37 -30
  340. package/docs/2.auth/1.introduction.md +33 -35
  341. package/docs/2.auth/2.authentication.md +60 -38
  342. package/docs/2.auth/3.tokens-and-sessions.md +56 -55
  343. package/docs/2.auth/4.users.md +82 -52
  344. package/docs/2.auth/5.external-identities.md +25 -26
  345. package/docs/3.base/1.introduction.md +64 -60
  346. package/docs/3.base/10.display-blocks.md +15 -16
  347. package/docs/3.base/11.page-layouts.md +273 -0
  348. package/docs/3.base/12.table-row-actions.md +122 -0
  349. package/docs/3.base/13.table-filters-and-forms.md +104 -0
  350. package/docs/3.base/14.table-events-and-helpers.md +114 -0
  351. package/docs/3.base/15.table-sources-and-gates.md +24 -0
  352. package/docs/3.base/2.types.md +44 -41
  353. package/docs/3.base/3.forms.md +27 -28
  354. package/docs/3.base/4.tables.md +34 -367
  355. package/docs/3.base/5.charts-and-widgets.md +28 -29
  356. package/docs/3.base/6.layout.md +21 -270
  357. package/docs/3.base/8.data-types.md +39 -40
  358. package/docs/3.base/9.helpers.md +36 -38
  359. package/docs/4.notifications/1.introduction.md +22 -26
  360. package/docs/4.notifications/2.categories-and-subjects.md +58 -55
  361. package/docs/4.notifications/3.sending.md +72 -68
  362. package/docs/5.html-render/1.introduction.md +119 -81
  363. package/docs/5.html-render/2.built-in-emails.md +71 -51
  364. package/docs/index.md +18 -29
  365. package/docs/tsconfig.json +4 -0
  366. package/package.json +12 -12
  367. package/docs/images/emails-admin-invite.png +0 -0
  368. package/docs/images/emails-change-notice.png +0 -0
  369. package/docs/images/emails-change-verification.png +0 -0
  370. package/docs/images/emails-digest-logo-dark.png +0 -0
  371. package/docs/images/emails-digest-logo-light.png +0 -0
  372. package/docs/images/emails-digest.png +0 -0
  373. package/docs/images/emails-export-partial.png +0 -0
  374. package/docs/images/emails-export-ready.png +0 -0
  375. package/docs/images/emails-reset-password.png +0 -0
  376. package/docs/images/emails-two-factor.png +0 -0
  377. package/docs/images/emails-user-validation.png +0 -0
@@ -1,17 +1,16 @@
1
1
  ---
2
2
  title: "Tenancy"
3
- description: "The DMS serves several tenants from one backend."
3
+ description: "Schema constants, tenancy tables, request tenant resolution, access gates, membership writes, and the tenant lifecycle contract."
4
+ mode: reference
4
5
  ---
5
6
 
6
- # Tenancy
7
-
8
7
  ## Overview
9
8
 
10
- The DMS serves several tenants from one backend. Tenant-owned tables live in a per-tenant schema, and a model handle is bound to one tenant when you obtain it: `GetModel(TenantMemberModel, tenantId)` reads and writes that tenant's rows only. No query filters rows by tenant, and no table carries a `tenantId` column — the isolation is in the handle.
9
+ The DMS serves several tenants from one backend. Tenant-owned tables live in a per-tenant schema, and a model handle is bound to one tenant when you obtain it. `GetModel(TenantMemberModel, tenantId)` reads and writes the rows of that tenant only. No query filters rows by tenant, and no table carries a `tenantId` column. The handle carries the isolation.
11
10
 
12
- For a first implementation, follow [Multi-tenant data](/docs/auth-and-tenancy/multi-tenant): register the table in the tenant schema, protect the route, and inject a tenant-bound model. Use this reference for the schema constants and lifecycle contracts. **Resolving a tenant id is not an authorization check**; `getRequestTenantId` must run behind authentication.
11
+ For a first implementation, follow [Multi-tenant data](/docs/auth-and-tenancy/multi-tenant). It registers the table in the tenant schema, protects the route, and injects a tenant-bound model. This page lists the schema constants and the lifecycle contracts. Resolving a tenant id is not an authorization check, so `getRequestTenantId` must run behind authentication.
13
12
 
14
- The tenancy surface spans several subpaths:
13
+ The tenancy exports span several subpaths:
15
14
 
16
15
  | Subpath | Contains |
17
16
  | ------- | -------- |
@@ -21,6 +20,7 @@ The tenancy surface spans several subpaths:
21
20
  | `@antelopejs/interface-dms/tenant-scoped-model` | Injecting a tenant-bound model |
22
21
  | `@antelopejs/interface-dms/tenant-access` | Tenant access gates |
23
22
  | `@antelopejs/interface-dms/tenant-ownership` | Membership and ownership writes |
23
+ | `@antelopejs/interface-dms/tenant-members` | `changeTenantMembers`, the one write path for memberships and invitations |
24
24
  | `@antelopejs/interface-dms/tenant-lifecycle` | Producer admission and permanent closure before deletion |
25
25
  | `@antelopejs/interface-dms/utils/saas-mode` | Declaring SaaS mode |
26
26
  | `@antelopejs/interface-dms/data-controllers` | The built-in members and roles data controllers |
@@ -65,36 +65,35 @@ export class Invoice extends Table {
65
65
  }
66
66
  ```
67
67
 
68
- ## The Tenancy Tables
68
+ ## The tenancy tables
69
69
 
70
- All three tables and their models are exported from `@antelopejs/interface-dms/db`, along with their table-name constants `tenantsTableName`, `tenantMembersTableName` and `ROLES_TABLE_NAME`.
70
+ `@antelopejs/interface-dms/db` exports the three tables, their models, and the table-name constants `tenantsTableName`, `tenantMembersTableName` and `ROLES_TABLE_NAME`.
71
71
 
72
72
  ### `Tenant`
73
73
 
74
- Table `tenants` in `dms-core` — one row per tenant, shared across the backend.
74
+ Table `tenants` in `dms-core`. It holds one row per tenant, shared across the backend.
75
75
 
76
76
  | Field | Type | Notes |
77
77
  | ----- | ---- | ----- |
78
- | `_id` | `string` | Tenant id. This is the instance id every per-tenant model handle takes. |
78
+ | `_id` | `string` | Tenant id. Every per-tenant model handle takes it as the instance id. |
79
79
  | `name` | `string` | Display name. |
80
80
  | `createdAt` | `Date` | Creation time, indexed. |
81
81
  | `updatedAt` | `Date` | Update time, indexed. |
82
82
 
83
- `TenantModel` extends `BasicDataModel(Tenant, tenantsTableName)` and adds `getMany(ids: string[]): Promise<Tenant[]>`, which returns `[]` for an empty id list.
84
-
85
- Being a core-schema table, `TenantModel` is obtained without an instance id: `GetModel(TenantModel)`.
83
+ `TenantModel` extends `BasicDataModel(Tenant, tenantsTableName)` and adds `getMany(ids: string[]): Promise<Tenant[]>`, which returns `[]` for an empty id list. `tenants` is a core-schema table, so you obtain the model without an instance id: `GetModel(TenantModel)`.
86
84
 
87
85
  ### `TenantMember`
88
86
 
89
- Table `tenant_members` in `dms-tenant` — one row per member of the tenant the handle is bound to.
87
+ Table `tenant_members` in `dms-tenant`. It holds one row per member of the tenant the handle is bound to.
90
88
 
91
89
  | Field | Type | Notes |
92
90
  | ----- | ---- | ----- |
93
91
  | `_id` | `string` | Membership id. |
94
- | `userId` | `string` | Indexed, related to the `User` table of the [Auth](/docs/interfaces/auth/introduction) domain. |
92
+ | `userId` | `string` | Related to the `User` table of the [Auth](/docs/interfaces/auth/introduction) domain. Indexed across tenant instances. |
95
93
  | `roleIds` | `string[]` | Related to `Role`, many. The roles whose grants make up the member's base permission set. |
96
94
  | `isTenantOwner` | `boolean` | Whether the member owns the tenant. |
97
- | `joinedAt` | `Date` | Creation time, indexed. |
95
+ | `inviteDeliveryId` | `string`, optional | Ties this membership to the durable invite acceptance that created it. |
96
+ | `joinedAt` | `Date` | Creation time. Indexed across tenant instances. |
98
97
  | `invitedBy` | `string \| null` | Indexed, related to `User`. |
99
98
 
100
99
  `TenantMemberModel` extends `BasicDataModel(TenantMember, tenantMembersTableName)`:
@@ -104,16 +103,16 @@ Table `tenant_members` in `dms-tenant` — one row per member of the tenant the
104
103
  | `getByUser(userId)` | `Promise<TenantMember \| undefined>` |
105
104
  | `existsByUser(userId)` | `Promise<boolean>` |
106
105
  | `listAll()` | `Promise<TenantMember[]>` |
107
- | `listOwners()` | `Promise<TenantMember[]>` — members with `isTenantOwner` set |
106
+ | `listOwners()` | `Promise<TenantMember[]>`, the members with `isTenantOwner` set |
108
107
  | `listByUserWithTenantIds(userId)` | `Promise<Array<{ tenantId: string; member: TenantMember }>>` |
109
- | `getOldestByUser(userId)` | `Promise<{ tenantId: string; member: TenantMember } \| undefined>` — earliest `joinedAt` |
108
+ | `getOldestByUser(userId)` | `Promise<{ tenantId: string; member: TenantMember } \| undefined>`, the row with the earliest `joinedAt` |
110
109
  | `countOwnersExcluding(excludedUserIds)` | `Promise<number>` |
111
110
 
112
- The two methods that return a `tenantId` alongside the row report which tenant each row came from, which is what a lookup that spans instances needs.
111
+ `listByUserWithTenantIds` and `getOldestByUser` return the tenant each row came from. A lookup that spans tenant instances needs that id.
113
112
 
114
113
  ### `Role`
115
114
 
116
- Table `roles` in `dms-tenant` — the tenant's roles.
115
+ Table `roles` in `dms-tenant`. It holds the roles of the tenant.
117
116
 
118
117
  | Field | Type | Notes |
119
118
  | ----- | ---- | ----- |
@@ -121,13 +120,14 @@ Table `roles` in `dms-tenant` — the tenant's roles.
121
120
  | `createdAt` | `Date` | Creation time, indexed. |
122
121
  | `updatedAt` | `Date` | Update time, indexed. |
123
122
  | `name` | `string` | Indexed. |
123
+ | `description` | `string \| null`, optional | What the role is for, shown in the roles editor. Absent on older rows. |
124
124
  | `permissions` | `string[]` | Granted permission ids ([Permissions](/docs/interfaces/core/permissions)). |
125
125
 
126
126
  `RoleModel` extends `BasicDataModel(Role, ROLES_TABLE_NAME)` and adds `getByName(name): Promise<Role | undefined>`.
127
127
 
128
- ## Resolve the Request's Tenant
128
+ ## Resolve the request's tenant
129
129
 
130
- `getRequestTenantId(ctx)` returns the tenant of the request's **verified** credential, bound by the DMS auth decorators (`AuthUser`, `AuthRawUser`, `IfAuthUser` with a valid token, `AuthOwnerOnly`, `AuthUserWithPermission`, the auth a `PageController` installs), the tenant guards or `authenticateRequestUser`. Without a verified credential (anonymous request, invalid or forged token) it returns `DEFAULT_TENANT_ID`; a token is never read for its tenant before its signature, user and session are checked.
130
+ `getRequestTenantId(ctx)` returns the tenant of the request's verified credential. The DMS binds that credential in the auth decorators (`AuthUser`, `AuthRawUser`, `IfAuthUser` with a valid token, `AuthOwnerOnly`, `AuthUserWithPermission`, and the auth a `PageController` installs), in the tenant guards (`AuthTenantMember`, `AuthTenantOwner` and `authenticateTenantRequest`), and in `authenticateRequestUser`. Without a verified credential, such as an anonymous request or an invalid or forged token, it returns `DEFAULT_TENANT_ID`. The DMS never reads a token's tenant before it checks the token's signature, user and session. When the request's authentication failed, or its `Authorization` header changed after authentication, `getRequestTenantId` throws `HTTPResult(401)`.
131
131
 
132
132
  ```typescript
133
133
  import {
@@ -148,11 +148,11 @@ export class WorkspaceController extends Controller("/api/workspace") {
148
148
  }
149
149
  ```
150
150
 
151
- Call it in the handler, after the route's auth decorator ran. A parameter or property provider that may run before it uses `resolveRequestTenantId(ctx)` (async), which verifies a bearer token itself and binds its tenant; `TenantScopedModel` does. Resolving a tenant is still not authorization: check membership and permissions for the operation.
151
+ Call it in the handler, after the route's auth decorator ran. A parameter or property provider resolved alongside the auth decorator uses `resolveRequestTenantId(ctx)` instead. That async function waits for the tenant guards and credential checks the route's other providers start, and answers the tenant they verified. Without one, it verifies a bearer token itself and binds its tenant. A request without a verified credential reads as anonymous, and a credential verified on it later answers 401. `TenantScopedModel` uses it. A resolved tenant still authorizes nothing, so check membership and permissions for the operation.
152
152
 
153
- ## Scope a Model to the Request's Tenant
153
+ ## Scope a model to the request's tenant
154
154
 
155
- `TenantScopedModel(ModelClass)` is a parameter and property decorator that injects `GetModel(ModelClass, await resolveRequestTenantId(ctx))`: the tenant of the verified credential, `DEFAULT_TENANT_ID` without one.
155
+ `TenantScopedModel(ModelClass)` is a parameter and property decorator. It injects `GetModel(ModelClass, await resolveRequestTenantId(ctx))`, which is the tenant of the verified credential, or `DEFAULT_TENANT_ID` without one.
156
156
 
157
157
  ```typescript
158
158
  import { Controller, Get } from "@antelopejs/interface-api";
@@ -172,9 +172,9 @@ export class RoleController extends Controller("/api/roles") {
172
172
  }
173
173
  ```
174
174
 
175
- As a property decorator it fills a data controller's `@ModelReference()` field:
175
+ As a property decorator, it fills the `@ModelReference()` field of a data controller:
176
176
 
177
- ```typescript
177
+ ```typescript fragment
178
178
  import { ModelReference } from "@antelopejs/interface-data-api/metadata";
179
179
  import { RoleModel } from "@antelopejs/interface-dms/db";
180
180
  import { TenantScopedModel } from "@antelopejs/interface-dms/tenant-scoped-model";
@@ -185,13 +185,13 @@ import { TenantScopedModel } from "@antelopejs/interface-dms/tenant-scoped-model
185
185
  declare model: RoleModel;
186
186
  ```
187
187
 
188
- Only use it for a model whose table lives in `TENANT_SCHEMA_NAME` or another schema with one instance per tenant. For a core-schema table, take the model without an instance id.
188
+ Use it only for a model whose table lives in `TENANT_SCHEMA_NAME` or another schema with one instance per tenant. For a core-schema table, take the model without an instance id.
189
189
 
190
- ## Tenant Access Gates
190
+ ## Tenant access gates
191
191
 
192
- A gate is a binary, tenant-level check, evaluated at every tenant authorization surface. It answers whether the tenant may use the product at all, independently of any permission set.
192
+ A gate is a binary check at tenant level. It decides whether the tenant may use the product at all, independently of any permission set. The DMS evaluates the gates at every tenant authorization point: permission guards, the tenant member and owner guards, table view data routes, page and menu visibility, and realtime page access.
193
193
 
194
- ```typescript
194
+ ```typescript fragment
195
195
  import { RegisterTenantAccessGate } from "@antelopejs/interface-dms/tenant-access";
196
196
 
197
197
  export async function construct(): Promise<void> {
@@ -217,26 +217,24 @@ type TenantAccessGateFn = (
217
217
  ) => Promise<TenantAccessResult> | TenantAccessResult;
218
218
  ```
219
219
 
220
- `TenantAccessGateInfo` carries `id`, `gate` and `order`.
221
-
222
- Semantics the interface guarantees:
220
+ `TenantAccessGateInfo` carries `id`, `gate` and `order`. The interface guarantees these semantics:
223
221
 
224
- - Gates run in ascending `order`, ties in registration order; the first denial wins and its `code` is what reaches the client.
225
- - Registering an id twice replaces the earlier gate rather than adding a second one.
222
+ - Gates run in ascending `order`, and ties run in registration order. The first denial wins, and its `code` reaches the client.
223
+ - Registering an id a second time replaces the earlier gate. It does not add a second gate.
226
224
  - A gate that throws fails the request.
227
225
  - The registration is removed when the registering module unloads.
228
- - Register in `construct()` so the gate is active before the API serves requests.
226
+ - Register in `construct()`, so the gate is active before the API serves requests.
229
227
  - `userId` is `undefined` for an unauthenticated request.
230
228
 
231
- Two functions evaluate the gates:
229
+ Three functions evaluate the gates:
232
230
 
233
231
  | Function | Behavior |
234
232
  | -------- | -------- |
235
233
  | `CheckTenantAccess(userId, tenantId)` | Resolves to the first denial, or `{ allowed: true }`. |
236
- | `AssertTenantAccess(userId, tenantId)` | Calls `CheckTenantAccess` and throws a `403` whose body is the denying gate's `code`. |
237
- | `EvaluateTenantAccess(userId, tenantId)` | Server-side only. Resolves to every denial as `{ gateId, code }[]`, in gate order, `[]` when none; a gate that throws rejects the whole evaluation. |
234
+ | `AssertTenantAccess(userId, tenantId)` | Calls `CheckTenantAccess` and throws a `403` whose body is the `code` of the denying gate. |
235
+ | `EvaluateTenantAccess(userId, tenantId)` | Server-side only. Resolves to every denial as `TenantAccessDenial[]` (`{ gateId, code }`), in gate order, and to `[]` when no gate denies. A gate that throws rejects the whole evaluation. |
238
236
 
239
- ```typescript
237
+ ```typescript fragment
240
238
  import {
241
239
  AssertTenantAccess,
242
240
  CheckTenantAccess,
@@ -250,17 +248,17 @@ if (!access.allowed) {
250
248
  await AssertTenantAccess(user._id, tenantId); // or let it throw
251
249
  ```
252
250
 
253
- `AuthTenantOwner` and `AuthTenantMember` call `AssertTenantAccess` unless the route opts out with `bypassTenantAccessGate` ([Permissions](/docs/interfaces/core/permissions)).
251
+ `AuthTenantOwner` and `AuthTenantMember` call `AssertTenantAccess` unless the route opts out with `bypassTenantAccessGate` ([Permissions](/docs/interfaces/core/permissions)). A table view opts out with the option of the same name in `TableViewOptions`. Routes that must stay reachable for a denied tenant, such as billing or recovery, use these options.
254
252
 
255
- `EvaluateTenantAccess` exists for modules that must know whether a denial other than their own is in place, for example before lifting a hold: identify gates by `gateId`, never by `code`, which two gates may share. Its result is an observation, not an authorization receipt or an atomic snapshot of external state; revalidate before acting on it.
253
+ `EvaluateTenantAccess` is for a module that must know whether another denial is in place, for example before it lifts its own hold. Identify gates by `gateId`, never by `code`, because two gates may share a code. The result is an observation. It is not an authorization receipt or an atomic snapshot of external state, so revalidate before you act on it.
256
254
 
257
- Use a gate to deny a tenant outright; use a permissions resolver to shape what its users may do.
255
+ A gate denies a tenant outright. To shape what the users of a tenant may do, use a permissions resolver.
258
256
 
259
- ## Memberships and Ownership
257
+ ## Memberships and ownership
260
258
 
261
- `applyTenantOwnership(userModel, userId, tenantId, patch)` upserts a membership row for the user in the tenant.
259
+ `applyTenantOwnership(userModel, userId, tenantId, patch)` adds the user to the tenant, or sets the roles and ownership of an existing member.
262
260
 
263
- ```typescript
261
+ ```typescript fragment
264
262
  import { GetModel } from "@antelopejs/interface-database-decorators";
265
263
  import { applyTenantOwnership } from "@antelopejs/interface-dms/tenant-ownership";
266
264
  import { UserModel } from "@antelopejs/interface-dms/auth/db";
@@ -277,9 +275,10 @@ await applyTenantOwnership(GetModel(UserModel), userId, tenantId, {
277
275
  | ----- | ---- | ----------- |
278
276
  | `roleIds` | `string[]` | Roles the member holds after the write. Replaces the previous list. |
279
277
  | `isTenantOwner` | `boolean` | Ownership flag after the write. |
280
- | `invitedBy` | `string \| null` | Recorded on insert. Ignored when the membership row already exists. |
278
+ | `invitedBy` | `string \| null`, optional | Recorded on insert. Ignored when the membership row already exists. |
279
+ | `deliveryId` | `string`, optional | Durable invite delivery. It grants membership without replacing later edits. |
281
280
 
282
- `applyTenantOwnership` goes through [`changeTenantMembers`](#changetenantmembers), which fires `Hook.MEMBER_BEING_CHANGED` before any write, so a module can veto it. When the user is not yet a member, it also fires `Hook.MEMBER_BEING_ADDED` before the write and `Hook.MEMBER_ADDED` after it. For an existing member it updates `roleIds` and `isTenantOwner` and fires neither of those two ([Hooks](/docs/interfaces/core/hooks)).
281
+ `applyTenantOwnership` goes through [`changeTenantMembers`](#changetenantmembers), which fires `Hook.MEMBER_BEING_CHANGED` before any write, so a module can veto it. When the user is not a member yet, it also fires `Hook.MEMBER_BEING_ADDED` before the write and `Hook.MEMBER_ADDED` after it. For an existing member, it updates `roleIds` and `isTenantOwner` and fires neither of those two hooks ([Hooks](/docs/interfaces/core/hooks)).
283
282
 
284
283
  Two helpers keep the legacy platform-owner flag on the user row aligned with tenant ownership:
285
284
 
@@ -288,11 +287,11 @@ Two helpers keep the legacy platform-owner flag on the user row aligned with ten
288
287
  | `syncPlatformOwnerOnTenantOwnerChange` | `(userModel, userId, tenantId, nextIsTenantOwner: boolean) => Promise<void>` |
289
288
  | `clearPlatformOwnerOnMemberRemoval` | `(userModel, userId, tenantId, removedMemberWasTenantOwner: boolean) => Promise<void>` |
290
289
 
291
- Both mirror the value onto `User.owner`, and both are no-ops outside the default tenant and when the backend runs in [SaaS mode](#saas-mode) — where `User.owner` means platform ownership rather than tenant ownership. `clearPlatformOwnerOnMemberRemoval` additionally does nothing when the removed member was not a tenant owner. Call them after a write that changes ownership by a path other than `applyTenantOwnership`, which already mirrors on its own.
290
+ Both mirror the value onto `User.owner`. Both do nothing outside the default tenant, and nothing when the backend runs in [SaaS mode](#registersaasmode), where `User.owner` means platform ownership. `clearPlatformOwnerOnMemberRemoval` also does nothing when the removed member was not a tenant owner. Call them after a write that changes ownership without `applyTenantOwnership`, which mirrors the flag itself.
292
291
 
293
- ### SaaS Mode
292
+ ### `RegisterSaasMode`
294
293
 
295
- By default, the owners of the default tenant are the platform owners. In SaaS mode the two are independent: tenant owners are customers, and platform ownership is managed on its own. The SaaS module declares that mode itself with `RegisterSaasMode`; the DMS never infers it from the modules it finds loaded.
294
+ By default, the owners of the default tenant are the platform owners. In SaaS mode the two are independent. Tenant owners are customers, and platform ownership is managed separately. The SaaS module declares the mode itself with `RegisterSaasMode`. The DMS never infers it from the modules it finds loaded.
296
295
 
297
296
  ```typescript
298
297
  import { RegisterSaasMode } from "@antelopejs/interface-dms/utils/saas-mode";
@@ -304,82 +303,104 @@ export async function construct(): Promise<void> {
304
303
 
305
304
  While SaaS mode is on:
306
305
 
307
- - the default tenant's owner flag is no longer mirrored onto `User.owner`, by `applyTenantOwnership` or by the two helpers above;
308
- - removing members from the default tenant no longer refuses to remove the last platform owner.
306
+ - `applyTenantOwnership` and the two helpers above no longer mirror the default tenant's owner flag onto `User.owner`.
307
+ - Removing members from the default tenant no longer refuses to remove the last platform owner.
309
308
 
310
- The registration is removed when the registering module unloads, and the mode stays on while at least one registration is active. `isSaasMode()` resolves to the current state, `false` until a module registers. Both behaviours read it on every request, so it does not matter whether the SaaS module registers before or after the DMS starts.
309
+ The registration is removed when the registering module unloads. The mode stays on while at least one registration is active. `isSaasMode()` resolves to the current state, and to `false` until a module registers. Both behaviors read the mode on every request, so the SaaS module may register before or after the DMS starts.
311
310
 
312
- ## Tenant Lifecycle Admission
311
+ ## Tenant lifecycle admission
313
312
 
314
- Membership and invite producers coordinate with tenant deletion through `@antelopejs/interface-dms/tenant-lifecycle`. This is a write-lifecycle contract, not an authorization check or a transaction: authorize the caller separately.
313
+ Membership and invite producers coordinate with tenant deletion through `@antelopejs/interface-dms/tenant-lifecycle`. This contract orders writes against deletion. It is not an authorization check or a transaction, so authorize the caller separately.
315
314
 
316
315
  ### `changeTenantMembers`
317
316
 
318
- Every insert, update or delete of a `TenantMemberModel` or `UserInviteModel` row goes through one entry point, `changeTenantMembers` from `@antelopejs/interface-dms/tenant-members`. The DMS uses it for every membership and invitation write it makes: adding a member, changing a member's roles or ownership (from the members table or the owner route), removing a member, moving the holders of a deleted role, leaving each tenant when an account is deleted, and creating, editing, resending, cancelling, expiring and accepting invitations. A module writing these rows itself must use it too; there is no other supported write path, and a raw `GetModel(...).insert/update/delete` on them skips both the barrier and the policy hook.
317
+ Every insert, update or delete of a `TenantMemberModel` or `UserInviteModel` row goes through one entry point, `changeTenantMembers` from `@antelopejs/interface-dms/tenant-members`. The DMS uses it for every membership and invitation write it makes:
319
318
 
320
- ```typescript
319
+ - adding a member;
320
+ - changing a member's roles or ownership, from the members table or the owner route;
321
+ - removing a member, and moving the holders of a deleted role;
322
+ - leaving each tenant when an account is deleted;
323
+ - creating, editing, resending, cancelling, expiring and accepting invitations.
324
+
325
+ A module that writes these rows itself must use it too. There is no other supported write path. A raw `GetModel(...).insert`, `update` or `delete` on these rows skips both the barrier and the policy hook.
326
+
327
+ ```typescript fragment
321
328
  changeTenantMembers<T>(
322
329
  tenantId: string,
323
- plan: () => Promise<{ changes: MemberChange[]; write: () => Promise<T> }>,
330
+ plan: () => Promise<TenantMembersWrite<T>>,
324
331
  ): Promise<T>
332
+
333
+ interface TenantMembersWrite<T> {
334
+ changes: MemberChange[];
335
+ write: () => Promise<T>;
336
+ }
325
337
  ```
326
338
 
327
- It runs, in order:
339
+ It runs these steps in order:
328
340
 
329
- 1. `runTenantLifecycleOperation(tenantId, ...)`: refused at once, before anything is read, while a [barrier](#pausing-admission) is held or once admission is closed;
330
- 2. `plan()`, inside the admission, which reads the current rows and describes the change;
331
- 3. `Hook.MEMBER_BEING_CHANGED` once per `MemberChange`, with `{ tenantId, ...change }`. A throwing handler refuses the write, replays of an invitation acceptance included;
332
- 4. `write()`, then the admission is released as `runTenantLifecycleOperation` describes.
341
+ 1. `runTenantLifecycleOperation(tenantId, ...)`. While a [barrier](#pausing-admission) is held, or once admission is closed, the call is refused at once, before anything is read.
342
+ 2. `plan()`, inside the admission. It reads the current rows and describes the change.
343
+ 3. `Hook.MEMBER_BEING_CHANGED`, once per `MemberChange`, with `{ tenantId, ...change }`. A throwing handler refuses the write, and that includes replays of an invitation acceptance.
344
+ 4. `write()`. The admission is then released as `runTenantLifecycleOperation` describes.
333
345
 
334
346
  A `MemberChange` has a `kind`:
335
347
 
336
348
  | `kind` | Fields | When |
337
349
  | ------ | ------ | ---- |
338
- | `added` | `userId`, `isTenantOwner`, `roleIds`, `deliveryId?` | A user joins: direct add, or an invitation accepted (`deliveryId` set) |
339
- | `removed` | `userId`, `isTenantOwner` | A member is removed, or leaves by deleting their account |
340
- | `roles` | `userId`, `roleIds`, `previousRoleIds` | A member's roles change, including when a deleted role is reassigned |
341
- | `ownership` | `userId`, `isTenantOwner`, `previousIsTenantOwner` | A member is given or loses the owner role |
350
+ | `added` | `userId`, `isTenantOwner`, `roleIds`, `deliveryId?` | A user joins by direct add, or by an accepted invitation (`deliveryId` set). |
351
+ | `removed` | `userId`, `isTenantOwner` | A member is removed, or leaves by deleting their account. |
352
+ | `roles` | `userId`, `roleIds`, `previousRoleIds` | A member's roles change, including when a deleted role is reassigned. |
353
+ | `ownership` | `userId`, `isTenantOwner`, `previousIsTenantOwner` | A member is given or loses the owner role. |
342
354
 
343
- One edit setting both roles and ownership announces two changes. A write that changes no membership (an invitation created, edited or retired) passes `changes: []`: it is still admitted, and invitation creation keeps its own `Hook.INVITE_BEING_CREATED`. Do not call `changeTenantMembers` from inside another `changeTenantMembers` or `runTenantLifecycleOperation` callback.
355
+ One edit that sets both roles and ownership announces two changes. A write that changes no membership, such as an invitation created, edited or retired, passes `changes: []`. The write is still admitted, and invitation creation keeps its own `Hook.INVITE_BEING_CREATED`. Do not call `changeTenantMembers` from inside another `changeTenantMembers` or `runTenantLifecycleOperation` callback, because each call takes its own admission.
344
356
 
345
- Deleting an account makes one call per tenant the user belongs to, in turn. A tenant holding a barrier refuses its call, which stops the deletion there: the account and its remaining memberships stay, and the user can retry once the barrier is released.
357
+ Deleting an account makes one call per tenant the user belongs to, one after the other. A tenant that holds a barrier refuses its call, and the deletion stops there. The account and its remaining memberships stay, and the user can retry once the barrier is released.
346
358
 
347
359
  ### `runTenantLifecycleOperation`
348
360
 
349
- `runTenantLifecycleOperation<T>(tenantId: string, work: () => Promise<T>): Promise<T>` admits one invocation before calling `work`, then returns its result after releasing that invocation's admission. Closed admission rejects without running `work`. Membership and invitation writes use it through `changeTenantMembers`; call it directly only for other work that must coordinate with tenant deletion, and await every effect, with no detached writer.
361
+ `runTenantLifecycleOperation<T>(tenantId: string, work: () => Promise<T>): Promise<T>` admits one invocation, calls `work`, then releases that admission and returns the result. Closed admission rejects without running `work`. Membership and invitation writes use it through `changeTenantMembers`. Call it directly only for other work that must coordinate with tenant deletion. That work must await every effect and start no detached writer.
350
362
 
351
- A successful callback releases its admission. A deliberate HTTP 4xx refusal also attempts release and rethrows the original refusal; other failures retain admission because their effects may be indeterminate. Retrying is a new invocation and never retires the failed one's identity. There is no automatic expiry or reconciliation API.
363
+ A successful callback releases its admission. A deliberate refusal, an `HTTPResult` with a 4xx status, also releases its admission and rethrows the original refusal. The release is tried three times. If every try fails, the admission stays and the call rejects. After a refusal it rejects with an `AggregateError` of the refusal and the release failure. After a success it rejects with the release failure. Any other failure retains the admission, because its effects may be indeterminate. A retry is a new invocation and never retires the identity of the failed one. There is no automatic expiry and no reconciliation API.
352
364
 
353
- ### Pausing Admission
365
+ ### Pausing admission
354
366
 
355
367
  A module that must validate a tenant's inventory with no membership writer in flight, such as a plan downgrade, pauses admission instead of closing it:
356
368
 
357
369
  | Function | Behavior |
358
370
  | -------- | -------- |
359
- | `observeTenantLifecycle(tenantId)` | Resolves to `{ revision, barrierId, activeAttemptIds, closed }`. Never retires an attempt. |
360
- | `pauseTenantLifecycleAdmission(tenantId, barrierId, expectedRevision)` | Publishes the barrier with one compare-and-set on `expectedRevision`. Resolves `false` when the revision moved, a barrier is held or admission is closed. |
361
- | `resumeTenantLifecycleAdmission(tenantId, barrierId)` | Releases this barrier; with no barrier held, it invalidates the revision so a delayed `pause` fails. Resolves `false` for another owner's barrier or closed admission. |
371
+ | `observeTenantLifecycle(tenantId)` | Resolves to a `TenantLifecycleObservation`: `{ revision, barrierId, activeAttemptIds, closed }`. Never retires an attempt. |
372
+ | `pauseTenantLifecycleAdmission(tenantId, barrierId, expectedRevision)` | Publishes the barrier with one compare-and-set on `expectedRevision`. Resolves `false` when the revision moved, a barrier is held, or admission is closed. |
373
+ | `resumeTenantLifecycleAdmission(tenantId, barrierId)` | Releases this barrier. With no barrier held, it invalidates the revision so that a delayed `pause` fails. Resolves `false` for the barrier of another owner or for closed admission. |
374
+
375
+ While a barrier is held, `runTenantLifecycleOperation` rejects new work without running it, with an `HTTPResult` 409 (`Tenant lifecycle admission is paused`). The refusal writes nothing and retains no admission, and the caller may retry once the barrier is released. Admitted work keeps running.
362
376
 
363
- While a barrier is held, `runTenantLifecycleOperation` rejects new work without running it, with an `HTTPResult` 409 (`Tenant lifecycle admission is paused`): a deliberate refusal with nothing written and no admission retained, which the caller may retry once the barrier is released. Admitted work keeps running. Observe the revision before checking your own business intent, pause with that revision, recheck the intent, then wait for an observation with no active attempt before validating. A `false` from `pause` needs a new observation, not a blind retry. Use a unique `barrierId` per phase, and do not observe or pause from inside a `runTenantLifecycleOperation` callback: the caller would wait for itself.
377
+ Follow this order:
378
+
379
+ 1. Observe the revision before you check your own business intent.
380
+ 2. Pause with that revision.
381
+ 3. Check the intent again. If you abandon it, resume.
382
+ 4. Wait for an observation with no active attempt, then validate.
383
+
384
+ A `false` from `pause` needs a new observation, not a blind retry. Use a unique `barrierId` for each phase. Do not observe or pause from inside a `runTenantLifecycleOperation` callback, because the caller would wait for itself.
364
385
 
365
386
  ### `closeTenantLifecycleAdmission`
366
387
 
367
- `closeTenantLifecycleAdmission(tenantId: string): Promise<void>` permanently closes admission, then checks that no admitted operation remains. It rejects if work is active or indeterminate, or if the database cannot confirm the transition. Rejection can leave admission closed: it never authorizes deletion or reopening.
388
+ `closeTenantLifecycleAdmission(tenantId: string): Promise<void>` closes admission permanently, then checks that no admitted operation remains. It rejects if work is active or indeterminate, or if the database cannot confirm the transition. A rejection can leave admission closed. It never authorizes deletion or reopening.
368
389
 
369
- Call it before any destructive tenant phase, including deletion hooks. Do not delete business data when closure rejects. Preserve the `tenant_lifecycle` and `invite_resolutions` safety rows after deletion; dropping the tenant schema or recreating those rows can reopen admission. Mixed-version writers and raw model writes bypassing admission prevent a safe rollout. These table names describe a preservation obligation, not supported raw-model APIs.
390
+ Call it before any destructive tenant phase, including deletion hooks. Do not delete business data when closure rejects. Preserve the `tenant_lifecycle` and `invite_resolutions` safety rows after deletion, because dropping the tenant schema or recreating those rows can reopen admission. Writers on mixed versions, and raw model writes that bypass admission, prevent a safe rollout. These table names describe what to preserve. They are not supported raw-model APIs.
370
391
 
371
- The [backend-services guide](/docs/building/backend-services#tenant-deletion-closes-producer-admission-first) explains where this fits in a module's deletion flow.
392
+ The [backend services guide](/docs/building/backend-services#tenant-deletion-closes-producer-admission-first) shows where closure fits in a module's deletion flow.
372
393
 
373
- ## Built-in Data Controllers
394
+ ## Built-in data controllers
374
395
 
375
- The interface ships the two data controllers the built-in settings screens are built on. The DMS module registers both itself, so their routes exist as soon as it is loaded: reference them, do not register them again.
396
+ The interface ships the two data controllers behind the built-in settings screens. The DMS module registers both itself, so their routes exist as soon as it is loaded. Reference them, and do not register them again.
376
397
 
377
398
  | Export | Import from | Table | Base path |
378
399
  | ------ | ----------- | ----- | --------- |
379
400
  | `memberSettingDataAPI` | `@antelopejs/interface-dms/data-controllers` | `TenantMember` | `/api/tables/members` |
380
401
  | `roleSettingDataAPI` | `@antelopejs/interface-dms/data-controllers/roles` | `Role` | `/api/tables/roles` |
381
402
 
382
- The `/data-controllers` barrel re-exports the members controller only; the roles controller is imported from its own path.
403
+ The `/data-controllers` barrel re-exports the members controller only. Import the roles controller from its own path.
383
404
 
384
405
  ```typescript
385
406
  import { roleSettingDataAPI } from "@antelopejs/interface-dms/data-controllers/roles";
@@ -397,8 +418,13 @@ const reviewerRolesField = {
397
418
  };
398
419
  ```
399
420
 
400
- Both bind their model with `TenantScopedModel`, so they read and write the request's tenant. `memberSettingDataAPI` exposes the member's `name` and `email` joined from the `User` table, the member's roles as a plain relation onto `roleSettingDataAPI` (the DMS members page draws it with its own role picker, through the edit form's component slot), `isTenantOwner`, `joinedAt`, `isValidated` and a computed `connected` field; `roleSettingDataAPI` exposes `name`, `permissions`, `createdAt` and `updatedAt`. Reference either one as the relation target of your own columns when you need a member or role picker, or feed one to a `TableView` to render it.
421
+ Both bind their model with `TenantScopedModel`, so they read and write the request's tenant.
422
+
423
+ - `memberSettingDataAPI` exposes `_id`, `userId`, the member's `name`, `email`, `avatar`, `isValidated`, `lastActiveAt` and `twoFactorMethods` joined from the `User` table, the member's `roleIds` as a plain relation onto `roleSettingDataAPI`, `isTenantOwner`, and `joinedAt`. The DMS members page draws the roles with its own role picker, through the component slot of the edit form. The controller mounts every default table route except `new`, `archive` and `restore`.
424
+ - `roleSettingDataAPI` exposes `_id`, `name`, `permissions`, `createdAt` and `updatedAt`, with every default table route.
425
+
426
+ Reference either controller as the relation target of your own columns when you need a member or role picker, or pass one to a `TableView` to render it.
401
427
 
402
- ## Next Steps
428
+ ## Next steps
403
429
 
404
430
  [Invites](/docs/interfaces/core/invites) covers bringing a user into a tenant that has no membership row for them yet.