@antelopejs/interface-dms 0.10.4 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +126 -0
- package/README.md +21 -10
- package/dist/attachments.d.ts +15 -2
- package/dist/attachments.js +14 -1
- package/dist/attachments.js.map +1 -1
- package/dist/auth/db/models/sessions.model.d.ts +7 -5
- package/dist/auth/db/models/sessions.model.js +2 -9
- package/dist/auth/db/models/sessions.model.js.map +1 -1
- package/dist/auth/db/models/userExternalIdentities.model.d.ts +7 -5
- package/dist/auth/db/models/users.model.d.ts +7 -5
- package/dist/auth/db/tables/users.table.d.ts +2 -2
- package/dist/auth/db/tables/users.table.js.map +1 -1
- package/dist/auth/index.d.ts +4 -4
- package/dist/auth/internal/jwt-auth-decorator.d.ts +1 -1
- package/dist/auth/internal/jwt-auth-decorator.js +6 -3
- package/dist/auth/internal/jwt-auth-decorator.js.map +1 -1
- package/dist/auth/internal/request-authenticators.d.ts +12 -0
- package/dist/auth/internal/request-authenticators.js +53 -10
- package/dist/auth/internal/request-authenticators.js.map +1 -1
- package/dist/base/activity-feed.d.ts +34 -32
- package/dist/base/activity-feed.js +2 -0
- package/dist/base/activity-feed.js.map +1 -1
- package/dist/base/banner.d.ts +12 -10
- package/dist/base/banner.js +2 -0
- package/dist/base/banner.js.map +1 -1
- package/dist/base/block-registry/internal/describe.js +4 -12
- package/dist/base/block-registry/internal/describe.js.map +1 -1
- package/dist/base/block-registry/internal/ui.js +1 -1
- package/dist/base/block-registry/internal/ui.js.map +1 -1
- package/dist/base/card.d.ts +11 -9
- package/dist/base/card.js +2 -0
- package/dist/base/card.js.map +1 -1
- package/dist/base/chart-card.d.ts +4 -2
- package/dist/base/chart-card.js +2 -0
- package/dist/base/chart-card.js.map +1 -1
- package/dist/base/chart-schemas.d.ts +148 -148
- package/dist/base/chart.d.ts +65 -0
- package/dist/base/chart.js +65 -0
- package/dist/base/chart.js.map +1 -1
- package/dist/base/code-block.d.ts +5 -3
- package/dist/base/code-block.js +2 -0
- package/dist/base/code-block.js.map +1 -1
- package/dist/base/custom.d.ts +5 -0
- package/dist/base/custom.js +5 -0
- package/dist/base/custom.js.map +1 -1
- package/dist/base/data-types/default/address.d.ts +5 -0
- package/dist/base/data-types/default/address.js +5 -0
- package/dist/base/data-types/default/address.js.map +1 -1
- package/dist/base/data-types/default/choices.d.ts +20 -0
- package/dist/base/data-types/default/choices.js +20 -0
- package/dist/base/data-types/default/choices.js.map +1 -1
- package/dist/base/data-types/default/files.d.ts +22 -5
- package/dist/base/data-types/default/files.js +20 -0
- package/dist/base/data-types/default/files.js.map +1 -1
- package/dist/base/data-types/default/numbers.d.ts +15 -0
- package/dist/base/data-types/default/numbers.js +25 -9
- package/dist/base/data-types/default/numbers.js.map +1 -1
- package/dist/base/data-types/default/relations.d.ts +10 -2
- package/dist/base/data-types/default/relations.js +9 -6
- package/dist/base/data-types/default/relations.js.map +1 -1
- package/dist/base/data-types/default/strings.d.ts +25 -0
- package/dist/base/data-types/default/strings.js +25 -0
- package/dist/base/data-types/default/strings.js.map +1 -1
- package/dist/base/data-types/default-types.d.ts +1 -1
- package/dist/base/data-types/field-types.d.ts +10 -0
- package/dist/base/data-types/field-types.js +10 -0
- package/dist/base/data-types/field-types.js.map +1 -1
- package/dist/base/data-types/internal/relation-endpoints.js +1 -0
- package/dist/base/data-types/internal/relation-endpoints.js.map +1 -1
- package/dist/base/data-types/status-type.d.ts +5 -0
- package/dist/base/data-types/status-type.js +5 -0
- package/dist/base/data-types/status-type.js.map +1 -1
- package/dist/base/display.d.ts +6 -6
- package/dist/base/empty-state.d.ts +15 -13
- package/dist/base/empty-state.js +2 -0
- package/dist/base/empty-state.js.map +1 -1
- package/dist/base/form-block-schema.d.ts +50 -50
- package/dist/base/form-schema.d.ts +2 -0
- package/dist/base/form-schema.js +2 -0
- package/dist/base/form-schema.js.map +1 -1
- package/dist/base/grid.d.ts +4 -0
- package/dist/base/grid.js +4 -0
- package/dist/base/grid.js.map +1 -1
- package/dist/base/guard.d.ts +30 -28
- package/dist/base/guard.js +2 -0
- package/dist/base/guard.js.map +1 -1
- package/dist/base/helpers/archive-mode-helpers.d.ts +1 -1
- package/dist/base/helpers/file-refs.js +15 -15
- package/dist/base/helpers/file-refs.js.map +1 -1
- package/dist/base/internal/form-schema-validation.js +7 -1
- package/dist/base/internal/form-schema-validation.js.map +1 -1
- package/dist/base/internal/realtime-topics.js.map +1 -1
- package/dist/base/internal/searchable.d.ts +2 -1
- package/dist/base/internal/searchable.js +1 -3
- package/dist/base/internal/searchable.js.map +1 -1
- package/dist/base/key-value-list.d.ts +21 -19
- package/dist/base/key-value-list.js +2 -0
- package/dist/base/key-value-list.js.map +1 -1
- package/dist/base/kpi-card.d.ts +4 -2
- package/dist/base/kpi-card.js +2 -0
- package/dist/base/kpi-card.js.map +1 -1
- package/dist/base/live-toggle.d.ts +2 -0
- package/dist/base/live-toggle.js +2 -0
- package/dist/base/live-toggle.js.map +1 -1
- package/dist/base/meter.d.ts +23 -21
- package/dist/base/meter.js +2 -0
- package/dist/base/meter.js.map +1 -1
- package/dist/base/nav-card-grid.d.ts +24 -19
- package/dist/base/nav-card-grid.js +5 -0
- package/dist/base/nav-card-grid.js.map +1 -1
- package/dist/base/page-toolbar.d.ts +2 -0
- package/dist/base/page-toolbar.js +2 -0
- package/dist/base/page-toolbar.js.map +1 -1
- package/dist/base/period-selector.d.ts +3 -1
- package/dist/base/period-selector.js +2 -0
- package/dist/base/period-selector.js.map +1 -1
- package/dist/base/placeholder.d.ts +2 -0
- package/dist/base/placeholder.js +2 -0
- package/dist/base/placeholder.js.map +1 -1
- package/dist/base/resource-form-schema.d.ts +2 -2
- package/dist/base/searchable.d.ts +3 -2
- package/dist/base/searchable.js +2 -2
- package/dist/base/searchable.js.map +1 -1
- package/dist/base/section.d.ts +6 -2
- package/dist/base/section.js +4 -0
- package/dist/base/section.js.map +1 -1
- package/dist/base/stack.d.ts +6 -0
- package/dist/base/stack.js +6 -0
- package/dist/base/stack.js.map +1 -1
- package/dist/base/stat-group.d.ts +16 -14
- package/dist/base/stat-group.js +2 -0
- package/dist/base/stat-group.js.map +1 -1
- package/dist/base/tab.d.ts +10 -8
- package/dist/base/tab.js +2 -0
- package/dist/base/tab.js.map +1 -1
- package/dist/base/table-view/column-display.d.ts +1 -1
- package/dist/base/table-view/column-display.js.map +1 -1
- package/dist/base/table-view/data-functions.d.ts +10 -4
- package/dist/base/table-view/data-functions.js.map +1 -1
- package/dist/base/table-view/factory.d.ts +5 -0
- package/dist/base/table-view/factory.js +8 -4
- package/dist/base/table-view/factory.js.map +1 -1
- package/dist/base/table-view/internal/auth.d.ts +17 -3
- package/dist/base/table-view/internal/auth.js +26 -5
- package/dist/base/table-view/internal/auth.js.map +1 -1
- package/dist/base/table-view/internal/column-conditions.d.ts +8 -1
- package/dist/base/table-view/internal/column-conditions.js +25 -13
- package/dist/base/table-view/internal/column-conditions.js.map +1 -1
- package/dist/base/table-view/internal/column-display.d.ts +1 -2
- package/dist/base/table-view/internal/column-display.js +3 -9
- package/dist/base/table-view/internal/column-display.js.map +1 -1
- package/dist/base/table-view/internal/factory-helpers.d.ts +1 -1
- package/dist/base/table-view/internal/factory-helpers.js +17 -14
- package/dist/base/table-view/internal/factory-helpers.js.map +1 -1
- package/dist/base/table-view/internal/factory-lifecycle-created.d.ts +2 -3
- package/dist/base/table-view/internal/factory-lifecycle-created.js +5 -5
- package/dist/base/table-view/internal/factory-lifecycle-created.js.map +1 -1
- package/dist/base/table-view/internal/factory-lifecycle-filter.d.ts +2 -2
- package/dist/base/table-view/internal/factory-private-actions.d.ts +2 -2
- package/dist/base/table-view/internal/files.d.ts +2 -1
- package/dist/base/table-view/internal/files.js +14 -5
- package/dist/base/table-view/internal/files.js.map +1 -1
- package/dist/base/table-view/internal/footer.d.ts +3 -3
- package/dist/base/table-view/internal/footer.js +16 -15
- package/dist/base/table-view/internal/footer.js.map +1 -1
- package/dist/base/table-view/internal/guards.d.ts +27 -5
- package/dist/base/table-view/internal/guards.js +133 -49
- package/dist/base/table-view/internal/guards.js.map +1 -1
- package/dist/base/table-view/internal/meta-helpers.d.ts +1 -1
- package/dist/base/table-view/internal/meta-helpers.js +3 -4
- package/dist/base/table-view/internal/meta-helpers.js.map +1 -1
- package/dist/base/table-view/internal/meta.js +2 -3
- package/dist/base/table-view/internal/meta.js.map +1 -1
- package/dist/base/table-view/internal/option-contract.d.ts +9 -1
- package/dist/base/table-view/internal/option-footer.d.ts +2 -2
- package/dist/base/table-view/internal/option-row-actions.d.ts +1 -1
- package/dist/base/table-view/internal/realtime.d.ts +52 -11
- package/dist/base/table-view/internal/realtime.js +23 -16
- package/dist/base/table-view/internal/realtime.js.map +1 -1
- package/dist/base/table-view/internal/resource-form.d.ts +1 -1
- package/dist/base/table-view/internal/resource-form.js +1 -1
- package/dist/base/table-view/internal/resource-form.js.map +1 -1
- package/dist/base/table-view/internal/row-actions-schema.d.ts +12 -12
- package/dist/base/table-view/internal/row-rule-shape.d.ts +11 -5
- package/dist/base/table-view/internal/row-rule-shape.js +60 -30
- package/dist/base/table-view/internal/row-rule-shape.js.map +1 -1
- package/dist/base/table-view/internal/row-rules.d.ts +28 -1
- package/dist/base/table-view/internal/row-rules.js +8 -19
- package/dist/base/table-view/internal/row-rules.js.map +1 -1
- package/dist/base/table-view/internal/tabs.d.ts +1 -1
- package/dist/base/table-view/internal/tabs.js +1 -4
- package/dist/base/table-view/internal/tabs.js.map +1 -1
- package/dist/base/table-view/internal/validation.d.ts +2 -2
- package/dist/base/table-view/internal/validation.js +7 -7
- package/dist/base/table-view/internal/validation.js.map +1 -1
- package/dist/base/table-view/meta.d.ts +7 -6
- package/dist/base/table-view/meta.js +12 -7
- package/dist/base/table-view/meta.js.map +1 -1
- package/dist/base/table-view/resource-form.d.ts +3 -1
- package/dist/base/table-view/resource-form.js +2 -0
- package/dist/base/table-view/resource-form.js.map +1 -1
- package/dist/base/table-view/routes.d.ts +73 -21
- package/dist/base/table-view/routes.js +13 -10
- package/dist/base/table-view/routes.js.map +1 -1
- package/dist/base/table-view/schema.d.ts +157 -152
- package/dist/base/table-view/schema.js +2 -1
- package/dist/base/table-view/schema.js.map +1 -1
- package/dist/base/table-view/source.js +4 -1
- package/dist/base/table-view/source.js.map +1 -1
- package/dist/base/table-view.d.ts +1 -1
- package/dist/base/table-view.js.map +1 -1
- package/dist/base/top-list-card.d.ts +35 -33
- package/dist/base/top-list-card.js +2 -0
- package/dist/base/top-list-card.js.map +1 -1
- package/dist/base/types/error.js +5 -2
- package/dist/base/types/error.js.map +1 -1
- package/dist/base/types/guards.d.ts +4 -4
- package/dist/base/types/json.d.ts +1 -10
- package/dist/base/types/row-action.d.ts +7 -7
- package/dist/component-options/confirm-dialog.d.ts +2 -0
- package/dist/component-options/confirm-dialog.js.map +1 -1
- package/dist/component-options/data-type.d.ts +2 -2
- package/dist/component-options/data-type.js +7 -7
- package/dist/component-options/data-type.js.map +1 -1
- package/dist/component-options/page-layout.d.ts +3 -2
- package/dist/component-options/page-layout.js.map +1 -1
- package/dist/component-options/row-action.d.ts +14 -9
- package/dist/component-slots.d.ts +11 -2
- package/dist/component-slots.js +1 -0
- package/dist/component-slots.js.map +1 -1
- package/dist/component.d.ts +7 -8
- package/dist/component.js +17 -25
- package/dist/component.js.map +1 -1
- package/dist/data-controllers/members.d.ts +11 -7
- package/dist/data-controllers/members.js +1 -1
- package/dist/data-controllers/members.js.map +1 -1
- package/dist/data-controllers/roles.d.ts +32 -10
- package/dist/db/internal/exportJobs.model.d.ts +7 -5
- package/dist/db/internal/inviteResolutions.model.d.ts +7 -5
- package/dist/db/internal/tenantLifecycle.model.d.ts +15 -7
- package/dist/db/internal/tenantLifecycle.model.js +16 -7
- package/dist/db/internal/tenantLifecycle.model.js.map +1 -1
- package/dist/db/models/roles.model.d.ts +7 -5
- package/dist/db/models/tenantMembers.model.d.ts +7 -5
- package/dist/db/models/tenants.model.d.ts +7 -5
- package/dist/db/models/user_invites.model.d.ts +7 -5
- package/dist/guards.d.ts +1 -1
- package/dist/hooks.d.ts +1 -2
- package/dist/hooks.js +33 -26
- package/dist/hooks.js.map +1 -1
- package/dist/internal/attachments.d.ts +2 -2
- package/dist/internal/component-builder.js +2 -1
- package/dist/internal/component-builder.js.map +1 -1
- package/dist/internal/component-serialization.d.ts +2 -4
- package/dist/internal/component-serialization.js +4 -3
- package/dist/internal/component-serialization.js.map +1 -1
- package/dist/internal/component-slots.d.ts +19 -2
- package/dist/internal/component-slots.js +35 -12
- package/dist/internal/component-slots.js.map +1 -1
- package/dist/internal/component-watch-filter.d.ts +11 -0
- package/dist/internal/component-watch-filter.js +22 -0
- package/dist/internal/component-watch-filter.js.map +1 -0
- package/dist/internal/component-watches.js +0 -1
- package/dist/internal/component-watches.js.map +1 -1
- package/dist/internal/own-entry.d.ts +7 -0
- package/dist/internal/own-entry.js +13 -0
- package/dist/internal/own-entry.js.map +1 -0
- package/dist/internal/uploads.d.ts +30 -0
- package/dist/internal/uploads.js +19 -0
- package/dist/internal/uploads.js.map +1 -1
- package/dist/invite-extension-payloads.d.ts +14 -2
- package/dist/invite-extensions/internal/delivery.js +1 -2
- package/dist/invite-extensions/internal/delivery.js.map +1 -1
- package/dist/invite-extensions/internal/edit.d.ts +2 -1
- package/dist/invite-extensions/internal/edit.js +8 -9
- package/dist/invite-extensions/internal/edit.js.map +1 -1
- package/dist/invite-extensions/internal/form-slot.js +8 -15
- package/dist/invite-extensions/internal/form-slot.js.map +1 -1
- package/dist/invite-extensions/internal/payload-slices.d.ts +33 -3
- package/dist/invite-extensions/internal/payload-slices.js +84 -19
- package/dist/invite-extensions/internal/payload-slices.js.map +1 -1
- package/dist/invite-extensions/internal/types.d.ts +10 -1
- package/dist/invite-resolution.js +6 -4
- package/dist/invite-resolution.js.map +1 -1
- package/dist/json.d.ts +10 -0
- package/dist/json.js +3 -0
- package/dist/json.js.map +1 -0
- package/dist/layout-banners.d.ts +5 -2
- package/dist/layout-banners.js.map +1 -1
- package/dist/page/controllers.d.ts +2 -2
- package/dist/page/internal/extension-validation.js +3 -2
- package/dist/page/internal/extension-validation.js.map +1 -1
- package/dist/page/internal/metadata-layout.js +1 -1
- package/dist/page/internal/metadata-layout.js.map +1 -1
- package/dist/page/internal/metadata-publish.js +3 -2
- package/dist/page/internal/metadata-publish.js.map +1 -1
- package/dist/page/internal/metadata-registration.js +5 -4
- package/dist/page/internal/metadata-registration.js.map +1 -1
- package/dist/page/internal/metadata-upload-registration.d.ts +4 -2
- package/dist/page/internal/metadata-upload-registration.js +13 -4
- package/dist/page/internal/metadata-upload-registration.js.map +1 -1
- package/dist/page/internal/metadata-uploads.d.ts +5 -5
- package/dist/page/internal/metadata-uploads.js +4 -8
- package/dist/page/internal/metadata-uploads.js.map +1 -1
- package/dist/page/internal/page-record.d.ts +2 -3
- package/dist/page/internal/page-record.js +23 -16
- package/dist/page/internal/page-record.js.map +1 -1
- package/dist/page/layout-filter.js +17 -6
- package/dist/page/layout-filter.js.map +1 -1
- package/dist/page/metadata.js +5 -7
- package/dist/page/metadata.js.map +1 -1
- package/dist/request-tenant.d.ts +10 -4
- package/dist/request-tenant.js +16 -34
- package/dist/request-tenant.js.map +1 -1
- package/dist/tenant-export.d.ts +2 -1
- package/dist/tenant-lifecycle.d.ts +43 -4
- package/dist/tenant-lifecycle.js +97 -18
- package/dist/tenant-lifecycle.js.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/uploads.d.ts +1 -1
- package/dist/uploads.js +39 -60
- package/dist/uploads.js.map +1 -1
- package/dist/utils/fire-and-forget.js.map +1 -1
- package/dist/utils/internal/decorated-class.d.ts +14 -0
- package/dist/utils/internal/decorated-class.js +37 -0
- package/dist/utils/internal/decorated-class.js.map +1 -0
- package/dist/utils/internal/type-check.d.ts +9 -0
- package/dist/utils/internal/type-check.js +13 -5
- package/dist/utils/internal/type-check.js.map +1 -1
- package/dist/utils/internal/value-parser.d.ts +1 -1
- package/dist/utils/internal/value-parser.js +22 -9
- package/dist/utils/internal/value-parser.js.map +1 -1
- package/dist/watch-actions.d.ts +1 -1
- package/docs/1.core/1.introduction.md +82 -77
- package/docs/1.core/10.layout-banners.md +57 -45
- package/docs/1.core/11.modules-and-dynamic-menus.md +178 -0
- package/docs/1.core/12.frontend-modules-and-request-helpers.md +155 -0
- package/docs/1.core/13.tenant-data-export.md +120 -0
- package/docs/1.core/2.pages-and-modules.md +140 -367
- package/docs/1.core/3.components.md +21 -22
- package/docs/1.core/4.permissions.md +92 -71
- package/docs/1.core/5.tenancy.md +150 -86
- package/docs/1.core/6.invites.md +90 -65
- package/docs/1.core/7.hooks.md +96 -166
- package/docs/1.core/8.realtime.md +44 -45
- package/docs/1.core/9.quick-actions.md +37 -30
- package/docs/2.auth/1.introduction.md +33 -35
- package/docs/2.auth/2.authentication.md +60 -38
- package/docs/2.auth/3.tokens-and-sessions.md +56 -55
- package/docs/2.auth/4.users.md +79 -51
- package/docs/2.auth/5.external-identities.md +25 -26
- package/docs/3.base/1.introduction.md +64 -60
- package/docs/3.base/10.display-blocks.md +15 -16
- package/docs/3.base/11.page-layouts.md +270 -0
- package/docs/3.base/12.table-row-actions.md +122 -0
- package/docs/3.base/13.table-filters-and-forms.md +104 -0
- package/docs/3.base/14.table-events-and-helpers.md +114 -0
- package/docs/3.base/15.table-sources-and-gates.md +24 -0
- package/docs/3.base/2.types.md +44 -41
- package/docs/3.base/3.forms.md +27 -28
- package/docs/3.base/4.tables.md +35 -368
- package/docs/3.base/5.charts-and-widgets.md +28 -29
- package/docs/3.base/6.layout.md +21 -270
- package/docs/3.base/8.data-types.md +39 -40
- package/docs/3.base/9.helpers.md +36 -38
- package/docs/4.notifications/1.introduction.md +22 -26
- package/docs/4.notifications/2.categories-and-subjects.md +58 -55
- package/docs/4.notifications/3.sending.md +72 -68
- package/docs/5.html-render/1.introduction.md +119 -81
- package/docs/5.html-render/2.built-in-emails.md +71 -51
- package/docs/index.md +18 -29
- package/docs/tsconfig.json +4 -0
- package/package.json +11 -11
- package/docs/images/emails-admin-invite.png +0 -0
- package/docs/images/emails-change-notice.png +0 -0
- package/docs/images/emails-change-verification.png +0 -0
- package/docs/images/emails-digest-logo-dark.png +0 -0
- package/docs/images/emails-digest-logo-light.png +0 -0
- package/docs/images/emails-digest.png +0 -0
- package/docs/images/emails-export-partial.png +0 -0
- package/docs/images/emails-export-ready.png +0 -0
- package/docs/images/emails-reset-password.png +0 -0
- package/docs/images/emails-two-factor.png +0 -0
- package/docs/images/emails-user-validation.png +0 -0
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Categories and
|
|
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
|
-
|
|
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
|
|
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 [
|
|
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
|
|
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
|
|
35
|
-
| `category` |
|
|
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
|
|
45
|
-
| `togglePermission` | [`TogglePermission`](#toggle-permissions) (optional) |
|
|
46
|
-
| `tagKey` | `string` (optional) | Translation key of a short module tag
|
|
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
|
-
|
|
45
|
+
The DMS ignores a second category registered with an `id` that is already taken. The first declaration wins.
|
|
49
46
|
|
|
50
|
-
## Declaring a
|
|
47
|
+
## Declaring a subject
|
|
51
48
|
|
|
52
|
-
`NotificationSubject(id, subject)` registers a subject under an already
|
|
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
|
|
82
|
-
| `subject` |
|
|
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
|
|
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
|
|
93
|
-
| `badgeKey` | `string` (optional) | Translation key of a badge
|
|
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
|
|
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
|
-
|
|
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
|
|
96
|
+
### Declaration order
|
|
100
97
|
|
|
101
|
-
The
|
|
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
|
|
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
|
|
102
|
+
## Toggle permissions
|
|
106
103
|
|
|
107
|
-
`TogglePermission` controls whether a user
|
|
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
|
|
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"
|
|
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.
|
|
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
|
|
123
|
-
| `"default"` | The subject stays toggleable. |
|
|
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
|
-
|
|
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",
|
|
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`
|
|
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
|
|
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
|
|
163
|
+
| `"shared"` | The DMS groups the copies. Once one recipient reads the notification, it counts as read for the whole group. |
|
|
161
164
|
|
|
162
|
-
|
|
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
|
|
167
|
+
## Built-in categories and subjects
|
|
165
168
|
|
|
166
|
-
The DMS declares one category and five subjects of its own.
|
|
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
|
|
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
|
|
190
|
+
| `AutomationSubject` | `automation` | `"default"` | Messages that automations send. |
|
|
188
191
|
|
|
189
|
-
Their labels and descriptions use the `dms.notifications.*` translation keys
|
|
192
|
+
Their labels and descriptions use the `dms.notifications.*` translation keys that ship with the DMS.
|
|
190
193
|
|
|
191
|
-
## Importing the
|
|
194
|
+
## Importing the types
|
|
192
195
|
|
|
193
|
-
The declaration functions
|
|
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
|
|
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
|
-
|
|
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
|
|
215
|
+
## Next steps
|
|
213
216
|
|
|
214
|
-
- [Sending
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
65
|
-
| `.tone(value)` | `NotificationTone` (`"neutral"`, `"primary"`, `"success"`, `"warning"`, `"error"`) | Colour of the icon well the inbox lists the notification with. Optional
|
|
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
|
|
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
|
-
|
|
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",
|
|
78
|
+
.params({ device: "Firefox on Linux", country: "France" })
|
|
82
79
|
.subject(SecuritySubject)
|
|
83
|
-
.linkTo("/settings/user/
|
|
80
|
+
.linkTo("/settings/user/security")
|
|
84
81
|
.build()
|
|
85
82
|
.toUser(userId);
|
|
86
83
|
```
|
|
87
84
|
|
|
88
|
-
### Required
|
|
85
|
+
### Required fields are checked by the type system
|
|
89
86
|
|
|
90
|
-
`NotificationBuilder<Set>` tracks in its type parameter which fields you have supplied
|
|
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
|
-
|
|
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
|
|
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`
|
|
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
|
|
103
|
+
## Sending to a single user
|
|
108
104
|
|
|
109
|
-
`toUser(userId, options?)` sends one copy to one user. It accepts an idempotency key
|
|
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
|
|
132
|
+
## Sending to several users
|
|
137
133
|
|
|
138
|
-
`toUsers(userIds, options?)` sends to a list of user ids.
|
|
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
|
|
140
|
+
## Sending to roles
|
|
145
141
|
|
|
146
|
-
`toRoles(roleIds, options?)`
|
|
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
|
|
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
|
|
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"
|
|
169
|
-
| `idempotencyKey` | `string` (optional) | Stable event key, unique across producers
|
|
170
|
-
| `dedupe` | `boolean` (optional) | `false
|
|
171
|
-
| `tenantId` | `string` (optional, `toRoles` only
|
|
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
|
|
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
|
|
178
|
+
## Repeated sends
|
|
183
179
|
|
|
184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
186
|
+
## Retrying durable events
|
|
191
187
|
|
|
192
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
216
|
+
## Delivery rules
|
|
215
217
|
|
|
216
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
253
|
+
## Importing the types
|
|
253
254
|
|
|
254
|
-
`Notification()`
|
|
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
|
|
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
|
}
|