@antelopejs/interface-dms 0.10.4 → 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 (374) hide show
  1. package/CHANGELOG.md +19 -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 +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/guards.d.ts +1 -1
  242. package/dist/hooks.d.ts +1 -2
  243. package/dist/hooks.js +33 -26
  244. package/dist/hooks.js.map +1 -1
  245. package/dist/internal/attachments.d.ts +2 -2
  246. package/dist/internal/component-builder.js +2 -1
  247. package/dist/internal/component-builder.js.map +1 -1
  248. package/dist/internal/component-serialization.d.ts +2 -4
  249. package/dist/internal/component-serialization.js +4 -3
  250. package/dist/internal/component-serialization.js.map +1 -1
  251. package/dist/internal/component-slots.d.ts +19 -2
  252. package/dist/internal/component-slots.js +35 -12
  253. package/dist/internal/component-slots.js.map +1 -1
  254. package/dist/internal/component-watch-filter.d.ts +11 -0
  255. package/dist/internal/component-watch-filter.js +22 -0
  256. package/dist/internal/component-watch-filter.js.map +1 -0
  257. package/dist/internal/component-watches.js +0 -1
  258. package/dist/internal/component-watches.js.map +1 -1
  259. package/dist/internal/own-entry.d.ts +7 -0
  260. package/dist/internal/own-entry.js +13 -0
  261. package/dist/internal/own-entry.js.map +1 -0
  262. package/dist/invite-extension-payloads.d.ts +14 -2
  263. package/dist/invite-extensions/internal/delivery.js +1 -2
  264. package/dist/invite-extensions/internal/delivery.js.map +1 -1
  265. package/dist/invite-extensions/internal/edit.d.ts +2 -1
  266. package/dist/invite-extensions/internal/edit.js +8 -9
  267. package/dist/invite-extensions/internal/edit.js.map +1 -1
  268. package/dist/invite-extensions/internal/form-slot.js +8 -15
  269. package/dist/invite-extensions/internal/form-slot.js.map +1 -1
  270. package/dist/invite-extensions/internal/payload-slices.d.ts +33 -3
  271. package/dist/invite-extensions/internal/payload-slices.js +84 -19
  272. package/dist/invite-extensions/internal/payload-slices.js.map +1 -1
  273. package/dist/invite-extensions/internal/types.d.ts +10 -1
  274. package/dist/invite-resolution.js +6 -4
  275. package/dist/invite-resolution.js.map +1 -1
  276. package/dist/json.d.ts +10 -0
  277. package/dist/json.js +3 -0
  278. package/dist/json.js.map +1 -0
  279. package/dist/layout-banners.d.ts +5 -2
  280. package/dist/layout-banners.js.map +1 -1
  281. package/dist/page/controllers.d.ts +2 -2
  282. package/dist/page/internal/extension-validation.js +3 -2
  283. package/dist/page/internal/extension-validation.js.map +1 -1
  284. package/dist/page/internal/metadata-layout.js +1 -1
  285. package/dist/page/internal/metadata-layout.js.map +1 -1
  286. package/dist/page/internal/metadata-publish.js +3 -2
  287. package/dist/page/internal/metadata-publish.js.map +1 -1
  288. package/dist/page/internal/metadata-registration.js +5 -4
  289. package/dist/page/internal/metadata-registration.js.map +1 -1
  290. package/dist/page/internal/metadata-upload-registration.d.ts +3 -1
  291. package/dist/page/internal/metadata-upload-registration.js +9 -0
  292. package/dist/page/internal/metadata-upload-registration.js.map +1 -1
  293. package/dist/page/internal/metadata-uploads.d.ts +18 -5
  294. package/dist/page/internal/metadata-uploads.js +7 -8
  295. package/dist/page/internal/metadata-uploads.js.map +1 -1
  296. package/dist/page/internal/page-record.d.ts +2 -3
  297. package/dist/page/internal/page-record.js +19 -14
  298. package/dist/page/internal/page-record.js.map +1 -1
  299. package/dist/page/layout-filter.js +17 -6
  300. package/dist/page/layout-filter.js.map +1 -1
  301. package/dist/page/metadata.js +5 -7
  302. package/dist/page/metadata.js.map +1 -1
  303. package/dist/request-tenant.d.ts +10 -4
  304. package/dist/request-tenant.js +16 -34
  305. package/dist/request-tenant.js.map +1 -1
  306. package/dist/tenant-export.d.ts +2 -1
  307. package/dist/tenant-lifecycle.d.ts +13 -4
  308. package/dist/tenant-lifecycle.js +51 -18
  309. package/dist/tenant-lifecycle.js.map +1 -1
  310. package/dist/types.d.ts +20 -0
  311. package/dist/uploads.js +42 -59
  312. package/dist/uploads.js.map +1 -1
  313. package/dist/utils/fire-and-forget.js.map +1 -1
  314. package/dist/utils/internal/decorated-class.d.ts +14 -0
  315. package/dist/utils/internal/decorated-class.js +37 -0
  316. package/dist/utils/internal/decorated-class.js.map +1 -0
  317. package/dist/utils/internal/type-check.d.ts +9 -0
  318. package/dist/utils/internal/type-check.js +13 -5
  319. package/dist/utils/internal/type-check.js.map +1 -1
  320. package/dist/utils/internal/value-parser.d.ts +1 -1
  321. package/dist/utils/internal/value-parser.js +22 -9
  322. package/dist/utils/internal/value-parser.js.map +1 -1
  323. package/dist/watch-actions.d.ts +1 -1
  324. package/docs/1.core/1.introduction.md +82 -77
  325. package/docs/1.core/10.layout-banners.md +57 -45
  326. package/docs/1.core/11.modules-and-dynamic-menus.md +178 -0
  327. package/docs/1.core/12.frontend-modules-and-request-helpers.md +155 -0
  328. package/docs/1.core/13.tenant-data-export.md +120 -0
  329. package/docs/1.core/2.pages-and-modules.md +140 -367
  330. package/docs/1.core/3.components.md +21 -22
  331. package/docs/1.core/4.permissions.md +92 -71
  332. package/docs/1.core/5.tenancy.md +112 -86
  333. package/docs/1.core/6.invites.md +90 -65
  334. package/docs/1.core/7.hooks.md +96 -166
  335. package/docs/1.core/8.realtime.md +44 -45
  336. package/docs/1.core/9.quick-actions.md +37 -30
  337. package/docs/2.auth/1.introduction.md +33 -35
  338. package/docs/2.auth/2.authentication.md +60 -38
  339. package/docs/2.auth/3.tokens-and-sessions.md +56 -55
  340. package/docs/2.auth/4.users.md +79 -51
  341. package/docs/2.auth/5.external-identities.md +25 -26
  342. package/docs/3.base/1.introduction.md +64 -60
  343. package/docs/3.base/10.display-blocks.md +15 -16
  344. package/docs/3.base/11.page-layouts.md +273 -0
  345. package/docs/3.base/12.table-row-actions.md +122 -0
  346. package/docs/3.base/13.table-filters-and-forms.md +104 -0
  347. package/docs/3.base/14.table-events-and-helpers.md +114 -0
  348. package/docs/3.base/15.table-sources-and-gates.md +24 -0
  349. package/docs/3.base/2.types.md +44 -41
  350. package/docs/3.base/3.forms.md +27 -28
  351. package/docs/3.base/4.tables.md +34 -367
  352. package/docs/3.base/5.charts-and-widgets.md +28 -29
  353. package/docs/3.base/6.layout.md +21 -270
  354. package/docs/3.base/8.data-types.md +39 -40
  355. package/docs/3.base/9.helpers.md +36 -38
  356. package/docs/4.notifications/1.introduction.md +22 -26
  357. package/docs/4.notifications/2.categories-and-subjects.md +58 -55
  358. package/docs/4.notifications/3.sending.md +72 -68
  359. package/docs/5.html-render/1.introduction.md +119 -81
  360. package/docs/5.html-render/2.built-in-emails.md +71 -51
  361. package/docs/index.md +18 -29
  362. package/docs/tsconfig.json +4 -0
  363. package/package.json +11 -11
  364. package/docs/images/emails-admin-invite.png +0 -0
  365. package/docs/images/emails-change-notice.png +0 -0
  366. package/docs/images/emails-change-verification.png +0 -0
  367. package/docs/images/emails-digest-logo-dark.png +0 -0
  368. package/docs/images/emails-digest-logo-light.png +0 -0
  369. package/docs/images/emails-digest.png +0 -0
  370. package/docs/images/emails-export-partial.png +0 -0
  371. package/docs/images/emails-export-ready.png +0 -0
  372. package/docs/images/emails-reset-password.png +0 -0
  373. package/docs/images/emails-two-factor.png +0 -0
  374. package/docs/images/emails-user-validation.png +0 -0
@@ -1,21 +1,18 @@
1
1
  ---
2
- title: "Categories and Subjects"
2
+ title: "Categories and subjects"
3
3
  description: "Every notification is filed under a subject, and every subject belongs to a category."
4
+ mode: reference
4
5
  ---
5
6
 
6
- # Categories and Subjects
7
-
8
- ## Overview
9
-
10
- Every notification is filed under a **subject**, and every subject belongs to a **category**. That two-level taxonomy is what the notification settings screen groups by, and what user preferences are keyed on: a preference entry exists per `categoryId:subjectId` pair.
7
+ Every notification is filed under a **subject**, and every subject belongs to a **category**. The notification settings screen groups by these two levels, and the DMS keys user preferences on them. A preference entry exists per `categoryId:subjectId` pair.
11
8
 
12
9
  You declare categories and subjects by calling `NotificationCategory()` and `NotificationSubject()`. Both calls register the declaration with the DMS and return the info object, which you keep as a module-level constant. You pass the category constant to `NotificationSubject()`, and the subject constant to the builder's `.subject()` when you send.
13
10
 
14
- Declarations are values, not decorators: they run when the module that contains them is imported. Declare them at the top level of a module that is imported during startup, so the taxonomy is complete before the first notification is sent.
11
+ Declarations are function calls, not decorators. They run when the module that contains them is imported. Declare them at the top level of a module that is imported during startup, so the taxonomy is complete before the first notification is sent.
15
12
 
16
- The [notification preferences walkthrough](/docs/auth-and-tenancy/authentication#notification-preferences) shows the System category expanded, its locked General subject, and the Playground category and subject switched off. The Email column is marked Soon: these controls currently manage in-app delivery, not transactional authentication emails.
13
+ The [Notification preferences](/docs/auth-and-tenancy/authentication#notification-preferences) walkthrough shows the System category expanded to its subjects, and describes the locked General subject in prose. Its Email column is marked **Soon**. These switches control in-app delivery, not the transactional authentication emails.
17
14
 
18
- ## Declaring a Category
15
+ ## Declaring a category
19
16
 
20
17
  `NotificationCategory(id, category)` registers a category and returns the resulting `NotificationCategoryInfo`.
21
18
 
@@ -31,25 +28,25 @@ export const shopCategory = NotificationCategory("shop", {
31
28
 
32
29
  | Parameter | Description |
33
30
  | --------- | ----------- |
34
- | `id` | Identifier of the category. It becomes the first half of every preference key under this category. |
35
- | `category` | The rest of the `NotificationCategoryInfo` object — every field except `id`. |
31
+ | `id` | Identifier of the category. It is the first half of every preference key under this category. |
32
+ | `category` | Every field of `NotificationCategoryInfo` except `id`. |
36
33
 
37
- ### NotificationCategoryInfo
34
+ ### `NotificationCategoryInfo`
38
35
 
39
36
  | Option | Type | Description |
40
37
  | ------ | ---- | ----------- |
41
38
  | `id` | `string` | Category identifier. Supplied through the first argument of `NotificationCategory()`. |
42
39
  | `labelKey` | `string` | Translation key for the category name. |
43
40
  | `descriptionKey` | `string` (optional) | Translation key for a longer description. |
44
- | `icon` | `string` | Icon name, in the icon syntax used across the dashboard (for example `i-ph-shopping-cart`). |
45
- | `togglePermission` | [`TogglePermission`](#toggle-permissions) (optional) | Parent toggle stance applied when evaluating this category's subjects. It does not lock the category's own switch. |
46
- | `tagKey` | `string` (optional) | Translation key of a short module tag (for example `SaaS`) shown next to the category in the settings screen and on each of its notifications in the inbox. Without it, the inbox tags a notification with its subject's label. |
41
+ | `icon` | `string` | Icon name, in the icon syntax the dashboard uses, for example `i-ph-shopping-cart`. |
42
+ | `togglePermission` | [`TogglePermission`](#toggle-permissions) (optional) | The parent stance the DMS reads when it decides whether a subject of this category is locked. It does not lock the category's own switch by itself. |
43
+ | `tagKey` | `string` (optional) | Translation key of a short module tag, for example `SaaS`. The settings screen shows it next to the category, and the inbox shows it on each notification of the category. Without it, the inbox tags a notification with its subject's label. |
47
44
 
48
- Registering a second category with an `id` that is already taken is ignored: the first declaration wins.
45
+ The DMS ignores a second category registered with an `id` that is already taken. The first declaration wins.
49
46
 
50
- ## Declaring a Subject
47
+ ## Declaring a subject
51
48
 
52
- `NotificationSubject(id, subject)` registers a subject under an already-declared category and returns the resulting `NotificationSubjectInfo`.
49
+ `NotificationSubject(id, subject)` registers a subject under an already registered category and returns the resulting `NotificationSubjectInfo`.
53
50
 
54
51
  ```typescript
55
52
  import {
@@ -78,52 +75,58 @@ export const stockSubject = NotificationSubject("stock", {
78
75
 
79
76
  | Parameter | Description |
80
77
  | --------- | ----------- |
81
- | `id` | Identifier of the subject. It is unique within its category, not globally: the preference key is `categoryId:subjectId`. |
82
- | `subject` | The rest of the `NotificationSubjectInfo` object — every field except `id`. |
78
+ | `id` | Identifier of the subject. It is unique within its category, not across categories, because the preference key is `categoryId:subjectId`. |
79
+ | `subject` | Every field of `NotificationSubjectInfo` except `id`. |
83
80
 
84
- ### NotificationSubjectInfo
81
+ ### `NotificationSubjectInfo`
85
82
 
86
83
  | Option | Type | Description |
87
84
  | ------ | ---- | ----------- |
88
85
  | `id` | `string` | Subject identifier, unique within the category. Supplied through the first argument of `NotificationSubject()`. |
89
- | `category` | `NotificationCategoryInfo` | The category this subject belongs to — the value returned by `NotificationCategory()`. |
86
+ | `category` | `NotificationCategoryInfo` | The category this subject belongs to, as returned by `NotificationCategory()`. |
90
87
  | `labelKey` | `string` | Translation key for the subject name. |
91
88
  | `descriptionKey` | `string` (optional) | Translation key for a longer description. |
92
- | `togglePermission` | [`TogglePermission`](#toggle-permissions) (optional) | Whether users may turn this subject off, read together with the category's value. |
93
- | `badgeKey` | `string` (optional) | Translation key of a badge shown next to the subject label in the settings screen (for example `Developer mode`). |
89
+ | `togglePermission` | [`TogglePermission`](#toggle-permissions) (optional) | Whether users may turn this subject off. The DMS reads it together with the category's value. |
90
+ | `badgeKey` | `string` (optional) | Translation key of a badge that the settings screen shows next to the subject label, for example `Developer mode`. |
94
91
 
95
- A subject carries the whole category object, not just its id, so `subject.category.id` is always reachable from a subject you hold. That is how a send resolves the preference key from nothing but the subject you passed to the builder.
92
+ A subject carries the whole category object, not only its id, so `subject.category.id` is always available from a subject you hold. A send uses it to build the preference key from the subject you passed to the builder.
96
93
 
97
- Registering a second subject with an `id` already taken inside the same category is ignored, the same way categories are.
94
+ The DMS ignores a second subject registered with an `id` already taken inside the same category, as it does for categories.
98
95
 
99
- ### Declaration Order
96
+ ### Declaration order
100
97
 
101
- The category has to be registered before any of its subjects: a subject whose category is not registered yet is rejected and never enters the registry, so nothing sent against it is delivered. Registrations run in call order, so declaring the category above its subjects in the same file — as in the example above — is enough.
98
+ The DMS must have the category registered before any of its subjects. It refuses a subject whose category is not registered yet with the error `Category "<id>" is not registered. Register the category before its subjects.` The subject never enters the registry, so the DMS drops everything sent under it. Registrations run in call order, so declaring the category above its subjects in the same file is enough, as in the example above.
102
99
 
103
- Registrations are scoped to the module that made them: when that module is unloaded, its categories and subjects are unregistered.
100
+ Registrations belong to the module that made them. When that module is unloaded, the DMS unregisters its categories and subjects.
104
101
 
105
- ## Toggle Permissions
102
+ ## Toggle permissions
106
103
 
107
- `TogglePermission` controls whether a user is allowed to opt out of a subject from the notification settings screen.
104
+ `TogglePermission` controls whether a user may opt out of a subject.
108
105
 
109
106
  ```typescript
110
107
  type TogglePermission = "allowed" | "forbidden" | "default";
111
108
  ```
112
109
 
113
- The field is optional on both `NotificationCategoryInfo` and `NotificationSubjectInfo`, and the settings screen locks a subject's switch only when both of these hold:
110
+ The field is optional on both `NotificationCategoryInfo` and `NotificationSubjectInfo`. A subject is locked only when both of these conditions hold:
114
111
 
115
- - the subject declares `togglePermission: "forbidden"`, and
112
+ - the subject declares `togglePermission: "forbidden"`;
116
113
  - its category declares `togglePermission` as `"default"` or `"forbidden"`.
117
114
 
118
- Every other combination leaves the subject toggleable. A category's own switch is never locked: the value you put on a category acts as the parent half of the rule above and nothing else.
115
+ Every other combination leaves the subject toggleable. The value on a category acts only as the parent half of this rule. The settings screen disables a category's own switch only when every subject in it is locked, because the switch then has nothing to change.
119
116
 
120
117
  | Value | Meaning on a subject | Meaning on a category |
121
118
  | ----- | -------------------- | --------------------- |
122
- | `"forbidden"` | The user may not turn the subject off, provided the category declares `"default"` or `"forbidden"`. | Enables the subject-side lock. The category itself stays toggleable. |
123
- | `"default"` | The subject stays toggleable. | Enables the subject-side lock, same as `"forbidden"`. |
124
- | `"allowed"` | The subject stays toggleable, same as `"default"`. | Leaves every subject toggleable, same as omitting the field. |
119
+ | `"forbidden"` | The subject is locked, provided the category declares `"default"` or `"forbidden"`. | Allows the subject-side lock. |
120
+ | `"default"` | The subject stays toggleable. | Allows the subject-side lock, the same as `"forbidden"`. |
121
+ | `"allowed"` | The subject stays toggleable, the same as `"default"`. | Leaves every subject toggleable, the same as omitting the field. |
122
+
123
+ A locked subject behaves the same way everywhere:
125
124
 
126
- So for a notification a user must always receive, declare `togglePermission: "forbidden"` on the subject **and** `togglePermission: "default"` on the category that holds it. `SystemCategory` declares `"default"`, which is what makes `GeneralSubject` mandatory.
125
+ - The settings screen shows its switch disabled, with a hint.
126
+ - The preferences route refuses a change that turns it off, with HTTP 403.
127
+ - Every dispatch method delivers it whatever preference the user has stored for it.
128
+
129
+ For a notification a user must always receive, declare `togglePermission: "forbidden"` on the subject **and** `togglePermission: "default"` on its category. `SystemCategory` declares `"default"`, which is what makes `GeneralSubject` mandatory.
127
130
 
128
131
  ```typescript
129
132
  import {
@@ -134,7 +137,7 @@ import {
134
137
  export const alertCategory = NotificationCategory("alerts", {
135
138
  labelKey: "shop.notifications.categories.alerts",
136
139
  icon: "i-ph-warning",
137
- togglePermission: "default", // required for the forbidden subject below to lock
140
+ togglePermission: "default",
138
141
  });
139
142
 
140
143
  export const outageSubject = NotificationSubject("outage", {
@@ -144,9 +147,9 @@ export const outageSubject = NotificationSubject("outage", {
144
147
  });
145
148
  ```
146
149
 
147
- `togglePermission` only governs what the settings screen offers. It does not change what `.toUser()` and the other dispatch methods do with a preference the user has already stored — see [Delivery Rules](/docs/interfaces/notifications/sending#delivery-rules).
150
+ Without `togglePermission: "default"` on `alertCategory`, `outageSubject` would stay toggleable. See [Delivery rules](/docs/interfaces/notifications/sending#delivery-rules) for how a send reads the user's preference.
148
151
 
149
- ## Read Scope
152
+ ## Read scope
150
153
 
151
154
  `ReadScope` describes how the read state of a multi-recipient notification behaves.
152
155
 
@@ -157,13 +160,13 @@ type ReadScope = "individual" | "shared";
157
160
  | Value | Meaning |
158
161
  | ----- | ------- |
159
162
  | `"individual"` | Each recipient gets their own copy with its own read state. This is the default. |
160
- | `"shared"` | The copies are grouped: once one recipient reads the notification, it counts as read for the whole group. |
163
+ | `"shared"` | The DMS groups the copies. Once one recipient reads the notification, it counts as read for the whole group. |
161
164
 
162
- The scope is chosen per send, not per subject; you pass it through `SendOptions` on the multi-recipient dispatch methods. See [Send Options](/docs/interfaces/notifications/sending#send-options).
165
+ You choose the scope per send, not per subject. You pass it through `SendOptions` on the multi-recipient dispatch methods. See [Send options](/docs/interfaces/notifications/sending#send-options).
163
166
 
164
- ## Built-in Categories and Subjects
167
+ ## Built-in categories and subjects
165
168
 
166
- The DMS declares one category and five subjects of its own. Import them and pass them to `.subject()` when your notification fits one of them, instead of declaring a near-duplicate.
169
+ The DMS declares one category and five subjects of its own. When your notification fits one of them, import it and pass it to `.subject()` instead of declaring a near-duplicate.
167
170
 
168
171
  ```typescript
169
172
  import {
@@ -176,7 +179,7 @@ import {
176
179
  } from "@antelopejs/interface-dms/notifications";
177
180
  ```
178
181
 
179
- `SystemCategory` is the category all five subjects belong to. Its id is `system`, its icon is `i-ph-gear`, and its `togglePermission` is `"default"` — the parent value that lets `GeneralSubject`'s `"forbidden"` take effect.
182
+ `SystemCategory` is the category of all five subjects. Its id is `system`, its icon is `i-ph-gear`, and its `togglePermission` is `"default"`. That parent value lets the `"forbidden"` on `GeneralSubject` take effect.
180
183
 
181
184
  | Constant | Id | Toggle permission | Used for |
182
185
  | -------- | -- | ----------------- | -------- |
@@ -184,18 +187,18 @@ import {
184
187
  | `SecuritySubject` | `security` | `"default"` | Sign-ins, password and email changes, two-factor changes, backup-code regeneration. |
185
188
  | `AccountSubject` | `account` | `"default"` | Account lifecycle messages, such as the welcome notification. |
186
189
  | `CollaborationSubject` | `collaboration` | `"default"` | Messages about other people in the workspace, such as a collaborator joining. |
187
- | `AutomationSubject` | `automation` | `"default"` | Messages emitted by automations. |
190
+ | `AutomationSubject` | `automation` | `"default"` | Messages that automations send. |
188
191
 
189
- Their labels and descriptions use the `dms.notifications.*` translation keys shipped with the DMS.
192
+ Their labels and descriptions use the `dms.notifications.*` translation keys that ship with the DMS.
190
193
 
191
- ## Importing the Types
194
+ ## Importing the types
192
195
 
193
- The declaration functions and the built-in constants are exported from the interface root. The type definitions live in the `types` subpath:
196
+ The interface root exports the declaration functions, the built-in constants and every type of the domain. The `types` subpath exports the types alone.
194
197
 
195
198
  | Symbol | Import from |
196
199
  | ------ | ----------- |
197
- | `NotificationCategory`, `NotificationSubject`, `SystemCategory`, `GeneralSubject`, `SecuritySubject`, `AccountSubject`, `CollaborationSubject`, `AutomationSubject`, `NotificationTone` | `@antelopejs/interface-dms/notifications` |
198
- | `NotificationCategoryInfo`, `NotificationSubjectInfo`, `NotificationTone`, `TogglePermission`, `ReadScope` | `@antelopejs/interface-dms/notifications/types` |
200
+ | `NotificationCategory`, `NotificationSubject`, `SystemCategory`, `GeneralSubject`, `SecuritySubject`, `AccountSubject`, `CollaborationSubject`, `AutomationSubject` | `@antelopejs/interface-dms/notifications` |
201
+ | `NotificationCategoryInfo`, `NotificationSubjectInfo`, `NotificationTone`, `TogglePermission`, `ReadScope` | `@antelopejs/interface-dms/notifications` or `@antelopejs/interface-dms/notifications/types` |
199
202
 
200
203
  ```typescript
201
204
  import type { NotificationSubjectInfo } from "@antelopejs/interface-dms/notifications/types";
@@ -207,8 +210,8 @@ interface NotificationTemplate {
207
210
  }
208
211
  ```
209
212
 
210
- Typing your own helpers against `NotificationSubjectInfo` is the usual reason to reach for the `types` subpath: it lets a template table or a helper function accept any subject, built-in or your own.
213
+ A type such as `NotificationSubjectInfo` lets a template table or a helper function accept any subject, built-in or your own.
211
214
 
212
- ## Next Steps
215
+ ## Next steps
213
216
 
214
- - [Sending Notifications](/docs/interfaces/notifications/sending) - Build a notification against a subject and dispatch it to users, roles, or everyone
217
+ - [Sending notifications](/docs/interfaces/notifications/sending) covers building a notification under a subject and dispatching it to users, roles, or everyone.
@@ -1,17 +1,14 @@
1
1
  ---
2
- title: "Sending Notifications"
2
+ title: "Sending notifications"
3
3
  description: "Build a notification, then send it to users, roles or everyone, with delivery rules and send options."
4
+ mode: reference
4
5
  ---
5
6
 
6
- # Sending Notifications
7
+ Sending takes two steps. `Notification()` starts a builder that you fill field by field. `.build()` closes it and returns a `SendableNotification`, which carries the dispatch methods.
7
8
 
8
- ## Overview
9
+ The [Read notifications](/docs/auth-and-tenancy/authentication#read-notifications) walkthrough shows the open bell with one unread message. It describes in prose how a message reaches every open session of its recipient without a reload, and no other account. The [Notification preferences](/docs/auth-and-tenancy/authentication#notification-preferences) walkthrough explains that turning a subject off stops new notifications of that subject while the messages already in the inbox stay.
9
10
 
10
- Sending happens in two steps. `Notification()` starts a builder you fill field by field; `.build()` closes it and gives you a `SendableNotification`, which carries the dispatch methods.
11
-
12
- For the visible result, see the [single-recipient notification and second-session update](/docs/auth-and-tenancy/authentication#read-notifications). The walkthrough distinguishes delivery from read state and shows [a disabled subject preventing a new delivery](/docs/auth-and-tenancy/authentication#notification-preferences).
13
-
14
- ```typescript
11
+ ```typescript fragment
15
12
  import {
16
13
  GeneralSubject,
17
14
  Notification,
@@ -36,11 +33,11 @@ A `SendableNotification` is immutable and holds no recipient, so you can build o
36
33
  | `toRoles` | `(roleIds: string[], options?: RoleSendOptions) => Promise<void>` | Send to every user holding at least one of the roles, in `options.tenantId` when set. |
37
34
  | `broadcast` | `(options?: SendOptions) => Promise<void>` | Send to every user. |
38
35
 
39
- ## Building a Notification
36
+ ## Building a notification
40
37
 
41
38
  `Notification()` takes no arguments and returns a fresh `NotificationBuilder`. Every setter stores its value and returns the builder, so calls chain in any order.
42
39
 
43
- ```typescript
40
+ ```typescript fragment
44
41
  import { Notification } from "@antelopejs/interface-dms/notifications";
45
42
  import { orderSubject } from "./notifications";
46
43
 
@@ -56,19 +53,19 @@ const shipped = Notification()
56
53
 
57
54
  | Method | Type | Description |
58
55
  | ------ | ---- | ----------- |
59
- | `.icon(value)` | `string` | Icon shown next to the notification, in the dashboard icon syntax (for example `i-ph-truck`). Required. |
56
+ | `.icon(value)` | `string` | Icon shown next to the notification, in the dashboard icon syntax, for example `i-ph-truck`. Required. |
60
57
  | `.title(value)` | `string` | Title line. Required. |
61
58
  | `.description(value)` | `string` | Body line. Required. |
62
- | `.subject(subject)` | `NotificationSubjectInfo` | The subject the notification is filed under, which decides the category and the user preference it is gated on. Required. |
59
+ | `.subject(subject)` | `NotificationSubjectInfo` | The subject the notification is filed under. It decides the category and the user preference that gates delivery. Required. |
63
60
  | `.linkTo(value)` | `string` | Destination the notification links to, such as `/settings/user/profile`. Optional. |
64
- | `.params(value)` | `Record<string, string \| number>` | Interpolation values carried alongside the notification. Optional. |
65
- | `.tone(value)` | `NotificationTone` (`"neutral"`, `"primary"`, `"success"`, `"warning"`, `"error"`) | Colour of the icon well the inbox lists the notification with. Optional: without it the well is `primary` while unread and neutral once read. |
61
+ | `.params(value)` | `Record<string, string \| number>` | Interpolation values stored with the notification. Optional. |
62
+ | `.tone(value)` | `NotificationTone` (`"neutral"`, `"primary"`, `"success"`, `"warning"`, `"error"`) | Colour of the icon well that the inbox lists the notification with. Optional. Without it, the well is `primary` while unread and neutral once read. |
66
63
 
67
- The subject comes from [Categories and Subjects](/docs/interfaces/notifications/categories-and-subjects) — either one you declared with `NotificationSubject()` or one of the built-in constants.
64
+ The subject comes from [Categories and subjects](/docs/interfaces/notifications/categories-and-subjects). It is either one you declared with `NotificationSubject()` or one of the built-in constants.
68
65
 
69
- Titles and descriptions can be literal text or, following the DMS translation convention, a `$`-prefixed translation key; in the latter case the values passed to `.params()` are the interpolation values for that key. The DMS's own account notifications are written that way:
66
+ A title or a description is either literal text or a `$`-prefixed translation key, following the DMS translation convention. With a key, the values passed to `.params()` are the interpolation values for that key. The DMS writes its own account notifications that way:
70
67
 
71
- ```typescript
68
+ ```typescript fragment
72
69
  import {
73
70
  Notification,
74
71
  SecuritySubject,
@@ -78,35 +75,34 @@ await Notification()
78
75
  .icon("i-ph-sign-in")
79
76
  .title("$dms.notifications.messages.new_login.title")
80
77
  .description("$dms.notifications.messages.new_login.description")
81
- .params({ device: "Firefox on Linux", origin: " (203.0.113.4)" })
78
+ .params({ device: "Firefox on Linux", country: "France" })
82
79
  .subject(SecuritySubject)
83
- .linkTo("/settings/user/profile")
80
+ .linkTo("/settings/user/security")
84
81
  .build()
85
82
  .toUser(userId);
86
83
  ```
87
84
 
88
- ### Required Fields Are Checked by the Type System
85
+ ### Required fields are checked by the type system
89
86
 
90
- `NotificationBuilder<Set>` tracks in its type parameter which fields you have supplied: `.icon()` returns `NotificationBuilder<Set | "icon">`, `.title()` adds `"title"`, and so on. `build()` is declared with a `this` parameter that only accepts a builder whose `Set` covers `RequiredFields`:
87
+ `NotificationBuilder<Set>` tracks in its type parameter which fields you have supplied. `.icon()` returns `NotificationBuilder<Set | "icon">`, `.title()` adds `"title"`, and the other setters add their own names. `build()` declares a `this` parameter that only accepts a builder whose `Set` covers `RequiredFields`:
91
88
 
92
89
  ```typescript
93
90
  type RequiredFields = "icon" | "title" | "description" | "subject";
94
91
  ```
95
92
 
96
- So a chain that skips one of the four does not compile — `this` is typed `never` at the `build()` call:
93
+ A chain that skips one of the four does not compile, because `this` is typed `never` at the `build()` call:
97
94
 
98
- ```typescript
99
- // Compile error: description and subject were never supplied
95
+ ```typescript fragment
100
96
  const broken = Notification().icon("i-ph-bell").title("Hello").build();
101
97
  ```
102
98
 
103
- The check happens at the call site. `build()` also checks at runtime, for builders whose type parameter was supplied by hand or whose chain went through `any`: it throws `Notification is missing required fields: description, subject` naming the fields still missing. A field set to `undefined` counts as missing. `.linkTo()`, `.params()` and `.tone()` never affect the check.
99
+ That chain fails because it never supplies the description and the subject. The type check happens at the call site. `build()` also checks at runtime, for builders whose type parameter was written by hand or whose chain went through `any`. It throws `Notification is missing required fields: description, subject`, naming the fields still missing. A field set to `undefined` counts as missing. `.linkTo()`, `.params()` and `.tone()` never affect the check.
104
100
 
105
- `build()` returns a `SendableNotification` holding a frozen copy of the data, so reusing the builder afterwards cannot change a notification you already built.
101
+ `build()` returns a `SendableNotification` that holds a frozen copy of the data, so reusing the builder afterwards cannot change a notification you already built.
106
102
 
107
- ## Sending to a Single User
103
+ ## Sending to a single user
108
104
 
109
- `toUser(userId, options?)` sends one copy to one user. It accepts an idempotency key; `readScope` has no effect for this method.
105
+ `toUser(userId, options?)` sends one copy to one user. It accepts an idempotency key. `readScope` has no effect for this method.
110
106
 
111
107
  ```typescript
112
108
  import { Controller, Post } from "@antelopejs/interface-api";
@@ -133,44 +129,44 @@ export class NotificationDemoController extends Controller("/api/notification")
133
129
  }
134
130
  ```
135
131
 
136
- ## Sending to Several Users
132
+ ## Sending to several users
137
133
 
138
- `toUsers(userIds, options?)` sends to a list of user ids. Duplicate ids in the list produce a single notification per user. The examples in this section and the two below dispatch the `shipped` notification built earlier on this page.
134
+ `toUsers(userIds, options?)` sends to a list of user ids. A duplicate id in the list still produces one notification for that user. The examples in this section and the two after it dispatch the `shipped` notification built earlier on this page.
139
135
 
140
- ```typescript
136
+ ```typescript fragment
141
137
  await shipped.toUsers([ownerId, managerId], { readScope: "shared" });
142
138
  ```
143
139
 
144
- ## Sending to Roles
140
+ ## Sending to roles
145
141
 
146
- `toRoles(roleIds, options?)` resolves the users that hold any of the given roles and sends to them. An empty `roleIds` array sends nothing, and so does a set of roles nobody holds.
142
+ `toRoles(roleIds, options?)` finds the users that hold any of the given roles and sends to them. An empty `roleIds` array sends nothing, and so does a set of roles that nobody holds.
147
143
 
148
- ```typescript
144
+ ```typescript fragment
149
145
  await shipped.toRoles([adminRoleId], { readScope: "shared", tenantId });
150
146
  ```
151
147
 
152
- Role ids are the DMS role identifiers described in the DMS tenancy and permissions guide, and they belong to a tenant. `tenantId` (`RoleSendOptions`) restricts the recipients to that tenant's members; without it, membership is resolved across tenants, so a user holding the role in any tenant is included.
148
+ Role ids are the DMS role identifiers that the DMS tenancy and permissions guide describes, and each one belongs to a tenant. `tenantId` (`RoleSendOptions`) limits the recipients to that tenant's members. Without it, the DMS looks up the role holders in every tenant, so a user holding the role in any tenant is included.
153
149
 
154
150
  ## Broadcasting
155
151
 
156
152
  `broadcast(options?)` sends to every user.
157
153
 
158
- ```typescript
154
+ ```typescript fragment
159
155
  await shipped.broadcast();
160
156
  ```
161
157
 
162
- ## Send Options
158
+ ## Send options
163
159
 
164
160
  Every dispatch method accepts an optional `SendOptions` object.
165
161
 
166
162
  | Option | Type | Description |
167
163
  | ------ | ---- | ----------- |
168
- | `readScope` | `ReadScope` (optional) | Read-state grouping for a multi-recipient send. Defaults to `"individual"` — see [Read Scope](/docs/interfaces/notifications/categories-and-subjects#read-scope). |
169
- | `idempotencyKey` | `string` (optional) | Stable event key, unique across producers: prefix it with your module ID. Reusing it for a recipient preserves the original notification and dismissal state. |
170
- | `dedupe` | `boolean` (optional) | `false` stores every unkeyed send, even one identical to a notification the recipient got less than 10 seconds earlier. Defaults to `true`; see below. |
171
- | `tenantId` | `string` (optional, `toRoles` only: `RoleSendOptions`) | Only the members of this tenant holding the roles; every tenant when unset. |
164
+ | `readScope` | `ReadScope` (optional) | Read-state grouping for a multi-recipient send. Defaults to `"individual"`. See [Read scope](/docs/interfaces/notifications/categories-and-subjects#read-scope). |
165
+ | `idempotencyKey` | `string` (optional) | Stable event key, unique across producers. Prefix it with your module ID. Reusing it for a recipient preserves the original notification and its dismissal state. |
166
+ | `dedupe` | `boolean` (optional) | With `false`, the DMS stores every unkeyed send, even one identical to a notification the recipient got less than 10 seconds earlier. Defaults to `true`. See [Repeated sends](#repeated-sends). |
167
+ | `tenantId` | `string` (optional, `toRoles` only through `RoleSendOptions`) | Only the members of this tenant holding the roles. Every tenant when unset. |
172
168
 
173
- Pick `"shared"` for alerts that only need one person to act on them, and `"individual"` for anything each recipient has to see for themselves.
169
+ Pick `"shared"` for an alert that only needs one person to act on it, and `"individual"` for anything each recipient has to see.
174
170
 
175
171
  ```typescript
176
172
  import type { SendOptions } from "@antelopejs/interface-dms/notifications/types";
@@ -179,51 +175,56 @@ const forEveryone: SendOptions = { readScope: "individual" };
179
175
  const handleOnce: SendOptions = { readScope: "shared" };
180
176
  ```
181
177
 
182
- ## Repeated Sends
178
+ ## Repeated sends
183
179
 
184
- An unkeyed notification identical to one the same user received less than 10 seconds earlier is dropped: same recipient, title, description, params, link, category, subject, tone and icon. A form submitted twice, or a hook firing on every instance, leaves one row; two notices worded alike but filed under different subjects, or drawn with a different tone or icon, are two notices. A sender whose repeats are all meant to arrive (a counter ticking, a chat) passes `{ dedupe: false }` to any send method. The check holds across instances: the row ID derives from the recipient, the content and a 10-second window, so the database refuses the second insert, and a copy from the window before also counts. The same notification sent again later arrives as usual, and so does one whose params differ (a count, a timestamp). Keyed sends are deduplicated by their key instead, as described below.
180
+ The DMS drops an unkeyed notification identical to one the same user received less than 10 seconds earlier. Identical means the same recipient, title, description, params, link, category, subject, tone and icon. A form submitted twice, or a hook firing on every instance, leaves one row. Two notices worded alike but filed under different subjects, or drawn with a different tone or icon, are two notices.
185
181
 
186
- ## Retrying Durable Events
182
+ A sender whose repeats must all arrive, such as a counter or a chat, passes `{ dedupe: false }` to any send method.
187
183
 
188
- Use the same event key on every outbox retry. The first delivery stored under a key wins: a later send under the same key returns without storing anything, even when its content differs (a count recomputed on retry, a timestamp), so a producer that keys a notification per period does not have to guard against a duplicate-key error. The explicit method is equivalent to passing `idempotencyKey` to `toUsers`; its key argument takes precedence over a key in `options`.
184
+ The check holds across instances. The row ID derives from the recipient, the content and a 10-second window, so the database refuses the second insert. A copy stored in the previous window also counts. The same notification sent again later arrives as usual, and so does one whose params differ, such as a count or a timestamp. The DMS deduplicates keyed sends by their key instead, as the next section describes.
189
185
 
190
- The key is shared by every module of the deployment: the DMS stores one row per key and recipient, whatever module or category sent it. Because a reused key counts as already delivered, two modules that happen to build the same key for the same user would lose the second notification without any error. Namespace your keys with your module ID (`mailing:digest:2026-10-13T09`, not `digest:2026-10-13T09`).
186
+ ## Retrying durable events
191
187
 
192
- ```typescript
188
+ Use the same event key on every outbox retry. The first delivery stored under a key wins. A later send under the same key returns without storing anything, even when its content differs, such as a count recomputed on retry. A producer that keys a notification per period therefore needs no guard against a duplicate-key error. `toUsersIdempotently` is equivalent to passing `idempotencyKey` to `toUsers`. Its key argument takes precedence over a key in `options`.
189
+
190
+ Every module of the deployment shares the key space. The DMS stores one row per key and recipient, whatever module or category sent it. A reused key counts as already delivered, so when two modules build the same key for the same user, the second notification is lost without any error. Namespace your keys with your module ID, as in `mailing:digest:2026-10-13T09` rather than `digest:2026-10-13T09`.
191
+
192
+ ```typescript fragment
193
193
  await shipped.toUsers([ownerId, managerId], { idempotencyKey: event.id });
194
194
  await shipped.toUsersIdempotently([ownerId, managerId], event.id);
195
195
  ```
196
196
 
197
- The DMS derives a namespaced SHA-256 row ID from the event key and recipient ID and performs a plain atomic insert. After an insert error, it reads that exact ID and accepts only a matching stored delivery, including its subject, content, and read group. Other errors propagate so an outbox can retry; duplicate attempts never replace the row or reset its timestamps or read state.
197
+ The DMS derives a SHA-256 row ID from the event key and the recipient ID, and performs a plain insert. If the insert fails, it reads the row under that ID. When that row is a keyed delivery to the same recipient, the send returns without an error. Otherwise the DMS rethrows the insert error, so an outbox can retry. A repeated attempt never replaces the row or resets its timestamps or read state.
198
198
 
199
- Single and bulk dismissal retain keyed rows as hidden delivery receipts. Lists, previews, counts, individual lookups, and read operations exclude these rows; retries do not recreate them. Unkeyed notifications retain physical deletion and fresh delivery on every send outside the 10-second [repeat window](#repeated-sends). Receipts retain notification content indefinitely: do not hard-delete or expire these rows while their event keys can still be retried, and account for this retention when designing data-erasure policies.
199
+ Single and bulk dismissal keep keyed rows as hidden delivery receipts. Lists, previews, counts, single lookups and read operations exclude these rows, and retries do not recreate them. The DMS still deletes a dismissed unkeyed notification, and delivers an unkeyed send again outside the 10-second [repeat window](#repeated-sends). Receipts keep the notification content with no expiry. Do not delete or expire these rows while their event keys can still be retried, and account for this retention in your data-erasure policies.
200
200
 
201
- Shared-read sends derive a stable group ID from the event key. Recipient membership and preferences are evaluated again on each attempt, so a retry can add previously missing recipients without duplicating successful deliveries. A recipient added after another recipient has already marked the group read starts unread; retries preserve existing recipients' state rather than replaying past read actions.
201
+ A shared-read send derives a stable group ID from the event key. The DMS checks recipient membership and preferences again on each attempt, so a retry can add recipients that were missing before without duplicating the deliveries that succeeded. A recipient added after another recipient has marked the group read starts unread. Retries keep the state of the existing recipients and do not replay past read actions.
202
202
 
203
- Realtime publishing remains best-effort and occurs only after a newly inserted delivery. A crash or lost acknowledgement can leave a stored notification without a realtime event; retrying does not replay realtime. Delivery gates still apply, and skipped recipients do not receive receipts.
203
+ Realtime publishing is best-effort and happens only after a newly inserted delivery. A crash or a lost acknowledgement can leave a stored notification without a realtime event, and a retry does not replay it. The delivery rules still apply, and a skipped recipient gets no receipt.
204
204
 
205
- Consumers that support older DMS packages must fail closed instead of passing an option an old runtime silently ignores. Feature-detect the explicit method and retain pending outbox work until the DMS package and runtime are upgraded together to a version containing it:
205
+ Before it sends, every keyed dispatch asks the DMS implementation, through `SupportsIdempotency`, whether it supports keyed delivery. If the implementation does not answer `true`, the dispatch throws `DMS idempotent notification delivery is unavailable` and sends nothing, rather than delivering without deduplication.
206
206
 
207
- ```typescript
207
+ If your module can load against a DMS implementation that has no `toUsersIdempotently` method, check for the method and fail closed. Keep the pending outbox work until the DMS packages provide it:
208
+
209
+ ```typescript fragment
208
210
  if (!("toUsersIdempotently" in shipped) || typeof shipped.toUsersIdempotently !== "function") {
209
211
  throw new Error("DMS idempotent notification delivery is unavailable");
210
212
  }
211
213
  await shipped.toUsersIdempotently([ownerId, managerId], event.id);
212
214
  ```
213
215
 
214
- Every keyed dispatch also checks the implementation's `SupportsIdempotency` interface capability before sending. A new local builder paired with an old DMS implementation therefore rejects rather than silently delivering without deduplication.
216
+ ## Delivery rules
215
217
 
216
- ## Delivery Rules
218
+ Every dispatch method resolves to one delivery per user, so the DMS applies the same rules to each of them:
217
219
 
218
- The DMS applies the same rules to every dispatch method, since `toUsers`, `toRoles` and `broadcast` all resolve to per-user deliveries:
220
+ - **The subject must be registered.** The DMS drops a notification whose subject was never registered through `NotificationSubject()`. See [Declaration order](/docs/interfaces/notifications/categories-and-subjects#declaration-order).
221
+ - **The title must not be empty.** The DMS drops a notification whose title is empty or only whitespace, and logs `[DMS] Notification without a title not delivered`.
222
+ - **The user preference decides.** The DMS skips a delivery when the recipient has turned off the `categoryId:subjectId` pair in their notification settings. Preferences default to enabled, so a subject a user has never touched is delivered. A [locked subject](/docs/interfaces/notifications/categories-and-subjects#toggle-permissions) is delivered whatever the stored preference.
223
+ - **Skipping is silent.** A skipped delivery is not an error. The promise resolves normally, and there is no per-recipient result to inspect.
224
+ - **Each recipient gets a row.** Each recipient gets their own stored notification. With `readScope: "shared"`, the DMS ties those copies together for read state, and they stay separate rows.
225
+ - **Stored notifications are pushed live.** The DMS also publishes each new delivery on the recipient's realtime stream, so the dashboard's badge and preview update without a reload.
219
226
 
220
- - **The subject must be registered.** A notification whose subject was never declared through `NotificationSubject()` is dropped. This is what makes registration order matter — see [Declaration Order](/docs/interfaces/notifications/categories-and-subjects#declaration-order).
221
- - **The user preference is honored.** Delivery is skipped when the recipient has turned off the `categoryId:subjectId` pair in their notification settings. Preferences default to enabled, so a subject a user has never touched is delivered.
222
- - **Skipping is silent.** A gated-out delivery is not an error: the promise resolves normally, and there is no per-recipient result to inspect.
223
- - **Delivery is per recipient.** Each recipient gets their own stored notification. With `readScope: "shared"` those copies are tied together for read state; they are still separate rows.
224
- - **Stored notifications are pushed live.** Each delivery is also published on the recipient's realtime stream, so the dashboard's badge and preview update without a reload.
225
-
226
- Because a send can be skipped for reasons outside your control, treat notifications as best-effort: do not make a workflow depend on one having been stored. Wrapping the call so a failure cannot break the operation that triggered it is a common pattern:
227
+ A send can be skipped for reasons outside your control, so treat notifications as best-effort and do not make a workflow depend on one having been stored. To keep a failed send from breaking the operation that triggered it, wrap the call:
227
228
 
228
229
  ```typescript
229
230
  import { Logging } from "@antelopejs/interface-core/logging";
@@ -249,18 +250,18 @@ export async function notifyWelcome(userId: string, name: string) {
249
250
  }
250
251
  ```
251
252
 
252
- ## Importing the Types
253
+ ## Importing the types
253
254
 
254
- `Notification()` is exported from the interface root, and the data types from the `types` subpath. The two classes are exported from the modules that define them, which you only need when you want to name them explicitly:
255
+ The interface root exports `Notification()` and the data types. The `types` subpath exports the data types alone. The two classes come from the modules that define them, which you need only to name them explicitly:
255
256
 
256
257
  | Symbol | Import from |
257
258
  | ------ | ----------- |
258
259
  | `Notification` | `@antelopejs/interface-dms/notifications` |
259
- | `NotificationData`, `SendOptions`, `RequiredFields` | `@antelopejs/interface-dms/notifications/types` |
260
+ | `NotificationData`, `SendOptions`, `RoleSendOptions`, `RequiredFields` | `@antelopejs/interface-dms/notifications` or `@antelopejs/interface-dms/notifications/types` |
260
261
  | `NotificationBuilder` | `@antelopejs/interface-dms/notifications/builder` |
261
262
  | `SendableNotification` | `@antelopejs/interface-dms/notifications/sendable` |
262
263
 
263
- `NotificationData` is the shape the builder assembles — `icon`, `title`, `description` and `subject`, plus the optional `linkTo` and `params`. Naming it is useful when you carry notification content around before building it:
264
+ `NotificationData` is the shape the builder assembles. It holds `icon`, `title`, `description` and `subject`, plus the optional `linkTo`, `params` and `tone`. Name it when you carry notification content around before you build it:
264
265
 
265
266
  ```typescript
266
267
  import { Notification } from "@antelopejs/interface-dms/notifications";
@@ -280,6 +281,9 @@ function fromData(data: NotificationData): SendableNotification {
280
281
  if (data.params) {
281
282
  builder.params(data.params);
282
283
  }
284
+ if (data.tone) {
285
+ builder.tone(data.tone);
286
+ }
283
287
 
284
288
  return builder.build();
285
289
  }