@antelopejs/interface-dms 0.7.1 → 0.7.3

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 (75) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +70 -68
  3. package/dist/auth/db/models/sessions.model.d.ts +1 -0
  4. package/dist/auth/db/models/userExternalIdentities.model.d.ts +1 -0
  5. package/dist/auth/db/models/users.model.d.ts +1 -0
  6. package/dist/base/block-registry/index.d.ts +1 -0
  7. package/dist/base/block-registry/index.js +1 -0
  8. package/dist/base/block-registry/index.js.map +1 -1
  9. package/dist/base/block-registry/presentation.d.ts +21 -0
  10. package/dist/base/block-registry/presentation.js +108 -0
  11. package/dist/base/block-registry/presentation.js.map +1 -0
  12. package/dist/base/block-types.d.ts +1 -0
  13. package/dist/base/block-types.js +1 -0
  14. package/dist/base/block-types.js.map +1 -1
  15. package/dist/base/live-toggle.d.ts +2 -2
  16. package/dist/base/period-selector.d.ts +2 -2
  17. package/dist/base/placeholder.js +2 -2
  18. package/dist/base/placeholder.js.map +1 -1
  19. package/dist/base/table-view/schema.d.ts +4 -4
  20. package/dist/db/internal/exportJobs.model.d.ts +1 -0
  21. package/dist/db/internal/inviteResolutions.model.d.ts +1 -0
  22. package/dist/db/internal/tenantLifecycle.model.d.ts +1 -0
  23. package/dist/db/models/roles.model.d.ts +1 -0
  24. package/dist/db/models/tenantMembers.model.d.ts +1 -0
  25. package/dist/db/models/tenants.model.d.ts +1 -0
  26. package/dist/db/models/user_invites.model.d.ts +1 -0
  27. package/docs/.navigation.yml +2 -0
  28. package/docs/1.core/.navigation.yml +2 -0
  29. package/docs/1.core/1.introduction.md +169 -0
  30. package/docs/1.core/10.layout-banners.md +133 -0
  31. package/docs/1.core/2.pages-and-modules.md +649 -0
  32. package/docs/1.core/3.components.md +287 -0
  33. package/docs/1.core/4.permissions.md +279 -0
  34. package/docs/1.core/5.tenancy.md +358 -0
  35. package/docs/1.core/6.invites.md +239 -0
  36. package/docs/1.core/7.hooks.md +354 -0
  37. package/docs/1.core/8.realtime.md +147 -0
  38. package/docs/1.core/9.quick-actions.md +115 -0
  39. package/docs/2.auth/.navigation.yml +2 -0
  40. package/docs/2.auth/1.introduction.md +75 -0
  41. package/docs/2.auth/2.authentication.md +222 -0
  42. package/docs/2.auth/3.tokens-and-sessions.md +352 -0
  43. package/docs/2.auth/4.users.md +256 -0
  44. package/docs/2.auth/5.external-identities.md +122 -0
  45. package/docs/3.base/.navigation.yml +2 -0
  46. package/docs/3.base/1.introduction.md +202 -0
  47. package/docs/3.base/10.display-blocks.md +136 -0
  48. package/docs/3.base/2.types.md +402 -0
  49. package/docs/3.base/3.forms.md +349 -0
  50. package/docs/3.base/4.tables.md +608 -0
  51. package/docs/3.base/5.charts-and-widgets.md +466 -0
  52. package/docs/3.base/6.layout.md +437 -0
  53. package/docs/3.base/8.data-types.md +361 -0
  54. package/docs/3.base/9.helpers.md +414 -0
  55. package/docs/4.notifications/.navigation.yml +2 -0
  56. package/docs/4.notifications/1.introduction.md +81 -0
  57. package/docs/4.notifications/2.categories-and-subjects.md +214 -0
  58. package/docs/4.notifications/3.sending.md +285 -0
  59. package/docs/5.html-render/.navigation.yml +2 -0
  60. package/docs/5.html-render/1.introduction.md +386 -0
  61. package/docs/5.html-render/2.built-in-emails.md +157 -0
  62. package/docs/images/emails-admin-invite.png +0 -0
  63. package/docs/images/emails-change-notice.png +0 -0
  64. package/docs/images/emails-change-verification.png +0 -0
  65. package/docs/images/emails-digest-logo-dark.png +0 -0
  66. package/docs/images/emails-digest-logo-light.png +0 -0
  67. package/docs/images/emails-digest.png +0 -0
  68. package/docs/images/emails-export-partial.png +0 -0
  69. package/docs/images/emails-export-ready.png +0 -0
  70. package/docs/images/emails-reset-password.png +0 -0
  71. package/docs/images/emails-two-factor.png +0 -0
  72. package/docs/images/emails-user-validation.png +0 -0
  73. package/docs/index.md +109 -0
  74. package/docs/tsconfig.json +11 -0
  75. package/package.json +25 -18
@@ -0,0 +1,136 @@
1
+ ---
2
+ title: "Display Blocks"
3
+ description: "Seven builders cover the summaries and notices around a page's data: `StatGroup`, `KeyValueList`, `NavCardGrid`, `EmptyState`, `Banner`, `Card` and `CodeBlock`."
4
+ ---
5
+
6
+ # Display Blocks
7
+
8
+ ## Overview
9
+
10
+ Seven builders cover the summaries and notices around a page's data: `StatGroup`, `KeyValueList`, `NavCardGrid`, `EmptyState`, `Banner`, `Card` and `CodeBlock`. Each returns a `ComponentBuilder` and registers its block type with an options schema, so `ListBlockTypes()` describes it to a page builder. `Card` is a container; the others are leaves. The task-shaped chapter is [Display blocks](/docs/components/display-blocks).
11
+
12
+ ```typescript
13
+ import {
14
+ Banner,
15
+ Card,
16
+ CodeBlock,
17
+ EmptyState,
18
+ KeyValueList,
19
+ NavCardGrid,
20
+ StatGroup,
21
+ } from "@antelopejs/interface-dms/base";
22
+ ```
23
+
24
+ | Builder | Subpath | Block type | Component name | Group |
25
+ | -------------- | ---------------- | -------------- | -------------------------- | --------------- |
26
+ | `StatGroup` | `stat-group` | `StatGroup` | `dms-stat-group-block` | `visualization` |
27
+ | `KeyValueList` | `key-value-list` | `KeyValueList` | `dms-key-value-list-block` | `content` |
28
+ | `NavCardGrid` | `nav-card-grid` | `NavCardGrid` | `dms-nav-card-grid-block` | `content` |
29
+ | `EmptyState` | `empty-state` | `EmptyState` | `dms-empty-state-block` | `content` |
30
+ | `Banner` | `banner` | `Banner` | `dms-banner-block` | `content` |
31
+ | `Card` | `card` | `Card` | `dms-card-block` | `layout` |
32
+ | `CodeBlock` | `code-block` | `CodeBlock` | `dms-code-block` | `content` |
33
+
34
+ Every factory but `EmptyState` takes its options as optional, so a block placed before it is configured is the bare call `StatGroup()`. `EmptyState(options)` requires its options, `title` included.
35
+
36
+ ## Shared Vocabulary
37
+
38
+ `display` holds what the six share; the tones come from `types/tone`, also re-exported by the `base` barrel. `color` is for what is clicked (buttons, row actions, header actions, tabs), `tone` for what shows a state (pills, icon wells, banners, meters, notifications, blocks).
39
+
40
+ | Symbol | Kind | Value |
41
+ | ------------------------- | --------- | ------------------------------------------------------------------------------------------------------ |
42
+ | `TONES` / `Tone` | constant | `neutral`, `primary`, `secondary`, `success`, `warning`, `error`, `info` (`types/tone`). |
43
+ | `ICON_TONES` / `IconTone` | constant | `TONES` plus `muted`, the quiet icon well (`types/tone`). |
44
+ | `BlockLinkAction` | interface | `{ label, to, icon?, variant?, color? }`: a link button; `to` is a route, an `#anchor` or a URL, and takes the page URL tokens (`{{params.X}}`, `{{query.X}}`). |
45
+ | `BLOCK_ACTION_VARIANTS` | constant | `solid`, `outline`, `soft`, `subtle`, `ghost`, `link`. |
46
+ | `BlockItemsSource` | interface | `{ fetchUrl?, fetchUrlMethod?, periodScope?, realtimeTopic?, empty?, skeletonCount? }`: where list items come from. |
47
+ | `BlockEmptyText` | interface | `{ title, description? }`: the `empty` state of a list block. |
48
+ | `BlockFunctions` | namespace | `REFRESH_PAGE`: the watch function asking every data block of the page to read its data again. |
49
+ | `BlockLinkActionSchema` | schema | The zod schema of one action, with its `ui()` hints. |
50
+ | `ComposedText` | interface | `{ key, params?, plural? }`: a text composed in the reader's language from raw values (`types/composed-text`). |
51
+ | `BlockText` | type | `string \| ComposedText`: what a block text field accepts. |
52
+
53
+ A block that can drop its frame does so with `card?: boolean` (`KeyValueList`, `EmptyState`, and the `ActivityFeed`, `Meter` and `Section` of [Lists & settings](/docs/components/lists-and-settings)); `Card` itself has `padded`. In the page editor a `fetchUrl` is picked in the data-source picker (widget `dataSource`).
54
+
55
+ ## List Blocks
56
+
57
+ `StatGroupProps`, `KeyValueListProps` and `NavCardGridProps` — and `ActivityFeedProps` ([Lists & settings](/docs/components/lists-and-settings)) — extend `BaseComponentProps` and `BlockItemsSource`. Their `items` are static; a `fetchUrl` replaces them with the `items` of its answer, `{ items: T[] }`, in the same item shape. An `ActivityFeed`'s answer may add a `count` (`ActivityFeedResponse`), drawn beside its title in place of its `count` option. When there is nothing to list, the block shows `empty`.
58
+
59
+ `fetchUrl` may name the record of the page it is on: `{{params.X}}` is filled with the route parameter `X` of the page URL (`{{params.id:n}}` for the n-th of a repeated name), `{{query.X}}` with its query parameter `X`, as in a form's `fetchUrl`. The block reads it again when they change and requests nothing while a token has no value. The same holds for the `fetchUrl` of `KpiCard`, `TopListCard`, `Meter`, `ChartCard` and the charts, and for `TabProps.badgesUrl`.
60
+
61
+ `BlockFunctions.REFRESH_PAGE` (`"DmsComponent.Block.RefreshPage"`, no params in `WatchFunctionParamMap`) makes each of those blocks, and every `TableView` of the page, fetch again without remounting, keeping its values until the answer lands: `.watch(FormEvents.SUBMIT_SUCCESS, BlockFunctions.REFRESH_PAGE)` on a form that changes the record. The page header's buttons do it on their own after a change; frontend code calls `refreshPageBlocks()`.
62
+
63
+ `periodScope` names the `PeriodSelector` whose period is added to the request, as for a `KpiCard`; the block refetches when it changes. `realtimeTopic` (`string | string[]`) names the topics the backend publishes on when the items change: the builder registers each against its page (`RegisterPageTopic`), and the block refetches on each event, keeping its items on screen. `Meter` takes both options for its own `fetchUrl`, and `TabProps.realtimeTopic` does the same for `badgesUrl`.
64
+
65
+ `skeletonCount` is the number of placeholder items the block draws while `fetchUrl` loads, so it keeps its height when the items land. Optional. Defaults to `columns`, or 4 (`StatGroup`); 5 (`KeyValueList`); `columns`, or 3 (`NavCardGrid`); `maxItems`, or 3 (`ActivityFeed`).
66
+
67
+ | Item interface | Required | Optional |
68
+ | ------------------ | --------------------- | ---------------------------------------------------------------------------- |
69
+ | `StatGroupItem` | `eyebrow`, `value` | `id`, `icon`, `tone` (`IconTone`), `detail`, `detailTone`, `to` |
70
+ | `KeyValueListItem` | `label` | `id`, `value`, `type` (`KeyValueType`), `to`, `tone`, `detail`, `currency`, `copy`, `copyValue` |
71
+ | `NavCardItem` | `title`, `icon`, `to` | `id`, `description`, `iconTone`, `state`, `stateTone`, `tag`, `readout` |
72
+
73
+ | Props | Own options |
74
+ | ------------------- | --------------------------------------------------------- |
75
+ | `StatGroupProps` | `items`, `layout` (`StatGroupLayout`), `columns`, `label` |
76
+ | `KeyValueListProps` | `items`, `title`, `card`, `dense`, `columns`, `currency` |
77
+ | `NavCardGridProps` | `items`, `title`, `description`, `columns` |
78
+
79
+ `KeyValueListItem.copy` draws a copy button copying the shown value; `copyValue` (a string) copies that text instead and implies `copy`.
80
+
81
+ `STAT_GROUP_LAYOUTS` is `joined`, `cards`; `KEY_VALUE_TYPES` is `text`, `status`, `money`, `date`, `link`, `mono`.
82
+
83
+ ## Composed Texts
84
+
85
+ `StatGroupItem.eyebrow`, `.value` and `.detail`, `KeyValueListItem.label`, `.value` and `.detail`, `NavCardItem.title`, `.description`, `.state`, `.tag` and `.readout` lines, `ActivityFeedItem.title`, `.meta` entries, `.detail` and `.time`, `ActivityFeedProps.badge`, `MeterProps.hint` and `.valueLabel`, `MeterSegment.label`, `TopListItem.title`, `.description` and `.valueLabel`, `BannerProps.title` and `.description`, and a layout banner's `text` are typed `BlockText`: a plain string as before, or a `ComposedText` the frontend writes in the reader's locale. The block schemas accept both (`ValidateBlockOptions` refuses a parameter of an unknown `type`) and describe the field with the `text` widget. The task-shaped guide is [Composed texts](/docs/components/block-fundamentals#composed-texts).
86
+
87
+ `ActivityFeedItem.params` is a `Record<string, ComposedTextParam>`: the values a `$`-prefixed title or detail names, typed as a composed text's parameters, so a `count` picks the plural form. A plain string value is inserted as before, and a `$`-prefixed one is translated with the entry's other values.
88
+
89
+ | Symbol | Kind | Value |
90
+ | ------------------------------- | --------- | ------------------------------------------------------------------------------------------- |
91
+ | `ComposedText` | interface | `key` (an i18n key, `$` optional), `params?: Record<string, ComposedTextParam>`, `plural?`. |
92
+ | `ComposedTextParam` | type | `string \| number \| ComposedTextTypedParam \| ComposedText`. |
93
+ | `ComposedTextTypedParam` | type | The union of the five typed parameters below; `ComposedTextParamType` is their `type`. |
94
+ | `ComposedTextMoneyParam` | interface | `{ type: "money", value, currency }`: `value` in the currency's minor units. |
95
+ | `ComposedTextDateParam` | interface | `{ type: "date" \| "datetime", value, format? }`: `value` an ISO string or milliseconds. |
96
+ | `ComposedTextRelativeDateParam` | interface | `{ type: "relative", value }`: the distance from now. |
97
+ | `ComposedTextNumberParam` | interface | `{ type: "number", value, format? }`. |
98
+ | `ComposedTextCountParam` | interface | `{ type: "count", value }`: picks the plural form unless `plural` names another parameter. |
99
+ | `COMPOSED_TEXT_DATE_FORMATS` | constant | `day`, `short`, `medium`, `long` (`ComposedTextDateFormat`). |
100
+ | `COMPOSED_TEXT_NUMBER_FORMATS` | constant | `decimal`, `percent`, `compact` (`ComposedTextNumberFormat`). |
101
+
102
+ ## Static Blocks
103
+
104
+ | Props | Options |
105
+ | ----------------- | ----------------------------------------------------------------------------------------------------- |
106
+ | `EmptyStateProps` | `title`, `description`, `variant`, `icon`, `tone`, `hatched`, `size`, `actions`, `code` (`EmptyStateCode`), `card` |
107
+ | `BannerProps` | `title`, `description`, `tone` (`BannerTone`), `icon`, `size`, `actions`, `dismissible`, `dismissKey`, `fetchUrl`, `fetchUrlMethod`, `periodScope`, `realtimeTopic` |
108
+ | `CardProps` | `title`, `count`, `description`, `actions`, `footer`, `padded`, `variant` |
109
+
110
+ `EmptyStateCode` is `{ content, language?, copy? }`: a snippet between the text and the actions, drawn as a `CodeBlock`.
111
+
112
+ `EMPTY_STATE_VARIANTS` is `no-data`, `no-result`, `no-access`, `error`; `EMPTY_STATE_SIZES` is `sm`, `md`, `lg`. `BANNER_TONES` is `info`, `success`, `warning`, `error`, `primary`; `BANNER_SIZES` is `md`, `sm`. `CARD_VARIANTS` is `default`, `elevated`. The three props extend `BaseComponentProps` (`watchActions`) like the list blocks. `BannerTone` is `Extract<Tone, …>` over `BANNER_TONES`. A banner's `dismissKey` defaults to the block's id on its page (`welcome` for `.child("welcome", Banner(…))`), which holds when blocks are added or moved. A banner's `fetchUrl` answers a `BannerContent` (the content fields of `BannerProps`, its `actions` being `BannerAction[]`: a `BlockLinkAction` or a `BannerButtonAction`, the `label`, `icon`, `variant`, `color`, `target` and `confirm` of a `CustomButtonSerialized`), or `null`, `{}` or a 204 for no banner; its fields override the options one by one. `realtimeTopic` registers each topic on the page the banner is placed on, as a chart's does.
113
+
114
+ ## Code Block
115
+
116
+ `CodeBlockProps` extends `BaseComponentProps`: `code`, `language` (`CodeBlockLanguage`, default `text`), `copy` (default `true`), `title` (`BlockText`), `maxLines`, `wrap`, `fetchUrl` and `fetchUrlMethod`. `CODE_BLOCK_LANGUAGES` is `json`, `javascript`, `typescript`, `html`, `sql`, `shell`, `text`. A `fetchUrl` answers a `CodeBlockResponse`, `{ code, language? }`, which replaces `code` and `language`; it takes the `{{params.X}}` / `{{query.X}}` tokens and `BlockFunctions.REFRESH_PAGE` like the list blocks. `CodeBlock(options?)` takes its options as optional.
117
+
118
+ ## Card Slots
119
+
120
+ `Card` registers `container: true` and two slots, `CARD_ACTIONS_SLOT` (`"actions"`, next to the head's link buttons) and `CARD_FOOTER_SLOT` (`"footer"`, the foot band). Children without a slot render in the body; each slot holds one child.
121
+
122
+ ```typescript
123
+ Card({ title: "Connectors", count: 4 })
124
+ .child("list", KeyValueList({ card: false, items }))
125
+ .child("foot", HStack(), { slot: CARD_FOOTER_SLOT });
126
+ ```
127
+
128
+ ## Live Toggle
129
+
130
+ `LiveToggle(options?)` (subpath `live-toggle`, block type `LiveToggle`, component `dms-live-toggle-block`, group `data`) is the Live / Pause switch of a page declared with `live` (`PageLiveOptions` from `@antelopejs/interface-dms/page`). `LiveToggleProps` extends `BaseComponentProps`: `align` (`LIVE_TOGGLE_ALIGNS`: `left`, `center`, `right`; default `right`) `showUpdatedAt` (default `true`), the time since the page last read its data, counted in the browser, `variant` (`LIVE_TOGGLE_VARIANTS`: `segmented`, `pill`; default `segmented`) and `missedLabel`, an i18n key or text given `{count}` naming the missed events while paused. The state belongs to the page, not to the block; on a page without `live` the block renders nothing.
131
+
132
+ ```typescript
133
+ import { LiveToggle } from "@antelopejs/interface-dms/base/live-toggle";
134
+
135
+ static toolbar = HStack().child("spacer", Spacer()).child("live", LiveToggle());
136
+ ```
@@ -0,0 +1,402 @@
1
+ ---
2
+ title: "Shared Types"
3
+ description: "The `types` subpath holds the vocabulary every other part of the `base` domain is built from: the base props interface every component's props extend, the colour and size scales, the action targets a button or a row…"
4
+ ---
5
+
6
+ # Shared Types
7
+
8
+ ## Overview
9
+
10
+ The `types` subpath holds the vocabulary every other part of the `base` domain is built from: the base props interface every component's props extend, the colour and size scales, the action targets a button or a row action can point at, the rule algebra that decides whether a row action applies to a row, the guard signatures a table view runs before a mutation, and a handful of small enums and value unions.
11
+
12
+ Most of it is exported from one subpath:
13
+
14
+ ```typescript
15
+ import {
16
+ Color,
17
+ FormMode,
18
+ HttpMethod,
19
+ Size,
20
+ } from "@antelopejs/interface-dms/base/types";
21
+ ```
22
+
23
+ Four modules are not re-exported by that barrel and are imported from their own file: `types/action-target`, `types/row-action`, `types/row-action-operators` and `types/guards`. The guard types and `RowActionRule` are also re-exported by the table view, so they can equally be imported from `@antelopejs/interface-dms/base`.
24
+
25
+ | Symbols | Import from |
26
+ | --- | --- |
27
+ | `BaseComponentProps`, `ButtonVariant`, `Color`, `ColorValue`, `ComponentOrNoInput`, `CustomButton`, `CustomButtonSerialized`, `CustomButtonAvailability`, `CustomButtonUnavailability`, `CustomButtonHidden`, `ButtonColor`, `ErrorWithMessage`, `isErrorWithMessage`, `getErrorMessage`, `ExportStatus`, `ListableConfig`, `FieldMetadata`, `FormMode`, `DefaultValue`, `FormFieldValue`, `HttpMethod`, `JsonPrimitive`, `JsonValue`, `AxeOrientation`, `ReadonlyBehavior`, `ReadonlyBehaviorType`, `ReadonlyBehaviorConfig`, `Size`, `SizeValue`, `TONES`, `Tone`, `ICON_TONES`, `IconTone`, `ModalSize`, `ComponentId`, `WatchAction`, `WatchActionCondition`, `WatchFunctionParamMap`, `WatchFunctionId`, `WatchFunctionParams` | `@antelopejs/interface-dms/base/types` |
28
+ | `ActionTarget`, `ActionTargetSerialized` | `@antelopejs/interface-dms/base/types/action-target` |
29
+ | `RowActionRule`, `RowActionConfig`, `CustomRowAction`, `CustomRowActionSerialized`, `FieldEqualsRule`, `FieldNotEqualsRule`, `FieldInRule`, `FieldNotInRule`, `AndRule`, `OrRule`, `NotRule`, `LogicalRule` | `@antelopejs/interface-dms/base/types/row-action` |
30
+ | `FIELD_OPERATORS`, `FIELD_KEY`, `FieldOperatorKey`, `FieldRule`, `AnyFieldRule`, `LOGICAL_OPERATOR_KEYS`, `LogicalOperatorKey` | `@antelopejs/interface-dms/base/types/row-action-operators` |
31
+ | `EditGuardArgs`, `DeleteGuardArgs`, `BulkGuardArgs`, `NewGuardArgs`, `GuardFn`, `AfterWrite`, `TableViewGuards` | `@antelopejs/interface-dms/base/types/guards` |
32
+
33
+ ## Base Component Props
34
+
35
+ `BaseComponentProps` is the one field every component props interface inherits.
36
+
37
+ ```typescript
38
+ import type { BaseComponentProps } from "@antelopejs/interface-dms/base/types";
39
+ ```
40
+
41
+ Its declaration is shown below for reference; import the existing type rather than redeclaring it in your module:
42
+
43
+ ```typescript
44
+ import type { WatchAction } from "@antelopejs/interface-dms/base/types";
45
+
46
+ interface BaseComponentProps {
47
+ watchActions?: WatchAction[];
48
+ }
49
+ ```
50
+
51
+ `FormProps`, `TreeProps`, `TabProps`, `KpiCardProps`, `ChartCardProps`, `TopListCardProps`, `PeriodSelectorProps` and the chart props all extend it. The `ComponentBuilder` from `@antelopejs/interface-dms/component` strips a caller-supplied `watchActions` from the options it receives and fills the field itself from `.watch()` / `.watchOn()` calls, so the property is the transport rather than something you set by hand.
52
+
53
+ ### Watch Actions
54
+
55
+ A `WatchAction` binds an event emitted by a component to a function exposed by a component.
56
+
57
+ ```typescript
58
+ import type {
59
+ WatchAction,
60
+ WatchActionCondition,
61
+ } from "@antelopejs/interface-dms/base/types";
62
+ ```
63
+
64
+ | Field | Type | Description |
65
+ | --- | --- | --- |
66
+ | `component` | `string` | Identifier of the component that emits the event. `ComponentId.SELF` (`"$component:self"`) refers to the declaring component. |
67
+ | `event` | `string` | Event name, e.g. `FormEvents.SUBMIT_SUCCESS`. |
68
+ | `functionId` | `string` | Identifier of the function to invoke, e.g. `FormFunctions.SET_FIELD_HIDDEN`. |
69
+ | `params` | `Record<string, unknown>` | Arguments passed to the function. |
70
+ | `onParam` | `WatchActionCondition \| WatchActionCondition[]` | Only run when the event payload matches these `{ key, value }` pairs. |
71
+ | `requirePermission` | `string` | Permission id the viewer must hold for the action to be kept. |
72
+
73
+ `WatchFunctionParamMap` is an interface extended by declaration merging: each module that exposes a watch function adds its `functionId` and the exact parameter shape to the map. `WatchFunctionId` is `keyof WatchFunctionParamMap & string`, and `WatchFunctionParams<F>` resolves to the registered parameter shape for `F`, falling back to `Record<string, unknown> | undefined` for ids that are not in the map. `Form` and `TableView` both register their functions in this map — see [Forms](/docs/interfaces/base/forms) and [Tables](/docs/interfaces/base/tables).
74
+
75
+ ## Colors and Sizes
76
+
77
+ ```typescript
78
+ import {
79
+ Color,
80
+ type ColorValue,
81
+ type ModalSize,
82
+ Size,
83
+ type SizeValue,
84
+ } from "@antelopejs/interface-dms/base/types";
85
+ ```
86
+
87
+ `Color` is an enum of the semantic palette names: `primary`, `secondary`, `success`, `info`, `warning`, `error`, `neutral`. `ColorValue` is `Color | (string & {})`, so a component that accepts a `ColorValue` takes either an enum member or an arbitrary string while keeping the enum members in editor completion.
88
+
89
+ `TONES` (`Tone`, from `types/tone`) is `neutral`, `primary`, `secondary`, `success`, `warning`, `error`, `info`; `ICON_TONES` (`IconTone`) adds `muted`. `color` is for what is clicked (buttons, row actions, header actions, tabs), `tone` for what shows a state (pills, icon wells, banners, meters, notifications, blocks); see [Display Blocks](/docs/interfaces/base/display-blocks).
90
+
91
+ `Size` maps readable names onto the scale tokens: `tiny` → `"xs"`, `small` → `"sm"`, `medium` → `"md"`, `large` → `"lg"`, `huge` → `"xl"`. `SizeValue` widens it the same way `ColorValue` widens `Color`.
92
+
93
+ `ModalSize` is a separate, wider union used only for modal action targets: `"sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl"`.
94
+
95
+ ## Buttons
96
+
97
+ `ButtonVariant` is an enum with `solid`, `outline`, `soft`, `ghost` and `link`. `ButtonColor` is a plain string union — `"primary" | "secondary" | "success" | "error" | "warning" | "info" | "neutral"` — used by `CustomButton` in place of the `Color` enum.
98
+
99
+ `CustomButton` describes a toolbar button; it is what `TableViewOptions.customButtons` takes.
100
+
101
+ ```typescript
102
+ import { ButtonVariant } from "@antelopejs/interface-dms/base/types";
103
+ import type { CustomButton } from "@antelopejs/interface-dms/base/types";
104
+
105
+ const inviteButton: CustomButton = {
106
+ label: "$page.settings.members.invite.button",
107
+ icon: "i-ph-user-plus",
108
+ variant: ButtonVariant.solid,
109
+ color: "primary",
110
+ target: { type: "page", url: "/settings/users/invite" },
111
+ };
112
+ ```
113
+
114
+ | Option | Type | Description |
115
+ | --- | --- | --- |
116
+ | `id` | `string` | Names the button so a quick action can press it. Unique within its table. |
117
+ | `label` | `string` | Button label. Required. |
118
+ | `icon` | `string` | Icon identifier. |
119
+ | `variant` | `ButtonVariant` | Visual variant. |
120
+ | `color` | `ButtonColor` | Colour name. |
121
+ | `target` | `ActionTarget` | What pressing the button does. Required. |
122
+ | `permission` | `string \| Action` | Gate the button behind a permission. |
123
+ | `permissionId` | `string` | Absolute permission id the button requires; wins over `permission`. |
124
+ | `availability` | `CustomButtonAvailability` | Disable the button, with a reason, for a request it refuses. |
125
+
126
+ `permission` takes either form: a string names one of the owner's actions, relative to its permission id — the table's for a toolbar button (`"add"` → `<table permission id>.add`), the page's for a header button (`DefaultLayout({ headerActions })`) — and an `Action` references any component's action — another table's `add`, for instance. `permissionId` takes an absolute permission id (`"settings.workspace.invites"`) and wins when both are set. A button the caller lacks the permission for is stripped from the serialized options rather than disabled, so it never reaches the browser. A declared permission that cannot be resolved to an id — an action on a page that never registered — fails closed and strips the button too.
127
+
128
+ A button with an `id` can be exposed as a [quick action](/docs/interfaces/core/quick-actions) with a `"button"` target. The quick action opens what the button opens and inherits the button's `permission`: it is listed for exactly the callers the button is shown to.
129
+
130
+ `availability` is called per request with `{ tenantId, user }` once the permission passed. Returning `{ reason }` (a string or a `$` i18n key) serves the button disabled, with the reason in its tooltip; `undefined` leaves it enabled, and so does a resolver that throws. It only shapes the UI: the operation behind the button must still refuse on its own.
131
+
132
+ `CustomButtonSerialized` is `CustomButton` with `target` replaced by `ActionTargetSerialized` and `permission`, `permissionId` and `availability` removed, since both are applied server-side; `disabled` and `disabledReason` carry what `availability` answered. The table view produces it when it serializes its options.
133
+
134
+ ## `PermissionGate`
135
+
136
+ `PermissionGate`, imported from `@antelopejs/interface-dms/permission-gate`, names the shared optional `permission?: ButtonPermission` and `permissionId?: string` fields described above. `ButtonPermission` is `string | Action`; `permissionId` takes precedence. The shape is shared by permission-filtered buttons, row actions, table tabs and views. It does not authorize the route that a control calls: protect that route with a guard too.
137
+
138
+ ## Action Targets
139
+
140
+ `ActionTarget` is a discriminated union on `type`. It is what a custom button or a custom row action points at.
141
+
142
+ ```typescript
143
+ import type { ActionTarget } from "@antelopejs/interface-dms/base/types/action-target";
144
+ ```
145
+
146
+ | `type` | Fields |
147
+ | --- | --- |
148
+ | `"drawer"` | `component: Component`, `title?`, `description?`, `direction?: DrawerDirection` |
149
+ | `"modal"` | `component: Component`, `size?: ModalSize`, `title?`, `description?` |
150
+ | `"sidePanel"` | `component: Component`, plus `SidePanelTargetOptions`: `title?`, `rowTitle?`, `positionLabel?`, `deepLink?`, `notFound?` |
151
+ | `"page"` | `url: string`: the page URL's `{{params.X}}` / `{{query.X}}` (a query pair without a value left out), then on a row action the row's `{{row.field}}`; see [Template tokens](/docs/building/pages-and-components#template-tokens) |
152
+ | `"external"` | `url: string`, `newTab?: boolean`; the same tokens as `page` |
153
+ | `"download"` | `url: string`, `filename?: string` (`DownloadActionTarget`); the same tokens as `page`, the file name taking them as written. Fetched with the user's session and saved under `filename`, else the server's `Content-Disposition` name, else the URL's last segment; a failure is toasted |
154
+ | `"quickAction"` | `id: string` (`category:id`, or the bare id) |
155
+ | `"api"` | `url: string`, `method?: "GET" \| "POST" \| "PUT" \| "DELETE" \| "PATCH"`, `body?`, `copy?`, `successMessage: string`; the same tokens as `page` |
156
+ | `"exportJob"` | `url: string`, `method?: "GET" \| "POST"`, `statusUrl?`, `downloadUrl?`, `labels?`; the same tokens as `page`, plus `{{job.id}}` in `statusUrl` and `downloadUrl` |
157
+
158
+ `SidePanelTargetOptions` sets the header the DMS draws above a `sidePanel` target's component: `title` (the action's label by default; `$`: an i18n key), `rowTitle`, the title while a row is open, an i18n key or a literal receiving the row's fields (`"{method} {path}"`), `positionLabel`, an i18n key receiving `{ index }` (from 1) and `{ total }` for the row's position among the rows the table shows (the DMS's "{index} of {total}" by default), and `deepLink`, which keeps the open row in the URL like the action's own `deepLink` — on by default for a side panel, `false` opting out (the action's own `deepLink` wins when set), and `notFound` (`SidePanelNotFoundTexts`: `title?`, `description?`), the state the panel draws with a close button when the row a link names answers 404. The panel is non-modal and docked on the right, one at a time with a module's side panels.
159
+
160
+ `DrawerDirection` is `"top" | "right" | "bottom" | "left"`, the edge the drawer slides in from: `bottom` by default, while `left` and `right` open a side sheet 440px wide (a side panel's default width) spanning the window's height.
161
+
162
+ A target carries no confirmation: the action does (`CustomButton.confirm`, `CustomRowAction.confirm`, `RowActionConfig.confirm`), with one type, `ActionConfirm = ConfirmDialog | ConfirmDialogFrom` (see below). The `labels` object on `exportJob` accepts `title`, `exporting`, `downloading`, `successTitle`, `successMessage`, `errorTitle` and `retry`, all optional strings.
163
+
164
+ ```typescript
165
+ const validateEmail: ActionTarget = {
166
+ type: "api",
167
+ url: "/settings/workspace/members/{{row._id}}/validate-email",
168
+ method: "POST",
169
+ successMessage: "$page.settings.members.action.validate_email_success",
170
+ };
171
+ ```
172
+
173
+ ### Confirmations
174
+
175
+ ```typescript
176
+ import type {
177
+ ActionConfirm,
178
+ ConfirmDialog,
179
+ } from "@antelopejs/interface-dms/base/types/confirm-dialog";
180
+ ```
181
+
182
+ `ConfirmDialog` is the one confirmation every action asks in: `title`, `description?`, `params?` (interpolated into the texts, besides a row action's row fields; a numeric `count` pluralizes them), `icon?`, `color?` (`ButtonColor`), `confirmLabel?`, `cancelLabel?`, `impact?: ConfirmDialogImpact[]` (`{ icon, label, count? }`), `secondaryAction?: ConfirmDialogSecondaryAction` (`{ label, icon?, target }`, a button next to cancel and confirm that leaves the dialog open; its `target` is an `external` link or `{ type: "download", url, filename? }`, fetched with the user's session), `blocked?` (the dialog only explains why the action cannot run), `confirmText?` (type-to-confirm), `confirmTextCaseInsensitive?` (the typed text compared ignoring case), `fields?: FormField[]` and `component?: Component`. The values of `fields` are sent with the action's request — merged into an `api` target's `body`, the input winning — and a field error the server answers shows under its field. `ConfirmDialogFrom` is `{ from: string }`: a URL (on a row action, built-in or custom, the row's fields filled in, such as `{{row._id}}`, plus `{{row.id}}`, the row's id) answering a `ConfirmDialogSerialized`, which `serializeConfirmDialog(dialog)` builds. `ActionConfirm` is either.
183
+
184
+ `ActionTargetSerialized` mirrors the union exactly, with `component` on the `drawer`, `modal` and `sidePanel` variants replaced by `ComponentInfoSerialized`.
185
+
186
+ ## Row Action Rules
187
+
188
+ A row action rule decides, per row, whether an action applies. The same rule value is sent to the frontend so the row's menu matches what the backend will accept, and evaluated on the backend before an `edit`, `delete`, `archive` or `restore` runs.
189
+
190
+ ### Field Operators
191
+
192
+ `FIELD_OPERATORS` is the catalogue of leaf comparisons. Each entry declares whether the rule value is an array and carries the comparison itself.
193
+
194
+ ```typescript
195
+ import {
196
+ FIELD_KEY,
197
+ FIELD_OPERATORS,
198
+ LOGICAL_OPERATOR_KEYS,
199
+ } from "@antelopejs/interface-dms/base/types/row-action-operators";
200
+ ```
201
+
202
+ | Key | `isArray` | Comparison |
203
+ | --- | --- | --- |
204
+ | `equals` | `false` | `fieldValue === ruleValue` |
205
+ | `notEquals` | `false` | `fieldValue !== ruleValue` |
206
+ | `in` | `true` | `ruleValue.includes(fieldValue)` |
207
+ | `notIn` | `true` | `!ruleValue.includes(fieldValue)` |
208
+
209
+ `FieldOperatorKey` is `keyof typeof FIELD_OPERATORS`. `FIELD_KEY` is the literal `"field"`, the property name that carries the field name inside a leaf rule. `LOGICAL_OPERATOR_KEYS` is the tuple `["and", "or", "not"]` and `LogicalOperatorKey` is its element type.
210
+
211
+ ### Rule Shapes
212
+
213
+ A leaf rule is `{ field: K }` plus the operator key. `FieldRule<T, Op>` builds that shape for one operator over the keys of `T`, choosing `T[K][]` for the array operators and `T[K]` for the others. `AnyFieldRule<T>` is the union over all four operators.
214
+
215
+ ```typescript
216
+ import type {
217
+ AndRule,
218
+ FieldEqualsRule,
219
+ RowActionRule,
220
+ } from "@antelopejs/interface-dms/base/types/row-action";
221
+
222
+ interface Invoice extends Record<string, unknown> {
223
+ status: string;
224
+ archived: boolean;
225
+ }
226
+
227
+ const isDraft: FieldEqualsRule<Invoice> = { field: "status", equals: "draft" };
228
+
229
+ const editable: AndRule<Invoice> = {
230
+ and: [isDraft, { field: "archived", notEquals: true }],
231
+ };
232
+ ```
233
+
234
+ The four named leaf aliases are `FieldEqualsRule<T>`, `FieldNotEqualsRule<T>`, `FieldInRule<T>` and `FieldNotInRule<T>`, each an alias of `FieldRule<T, Op>` for the matching operator. The three combinators are `AndRule<T>` (`{ and: RowActionRule<T>[] }`), `OrRule<T>` (`{ or: RowActionRule<T>[] }`) and `NotRule<T>` (`{ not: RowActionRule<T> }`); `LogicalRule<T>` is their union. `RowActionRule<T>` is `AnyFieldRule<T> | LogicalRule<T>`, so rules nest to any depth.
235
+
236
+ ### Attaching a Rule
237
+
238
+ `RowActionConfig<T>` is the object form of a built-in row action.
239
+
240
+ | Option | Type | Description |
241
+ | --- | --- | --- |
242
+ | `isEnabled` | `boolean` | Whether the action can be triggered. |
243
+ | `isVisible` | `boolean` | Whether the action is shown. |
244
+ | `rule` | `RowActionRule<T>` | Per-row condition. |
245
+
246
+ `CustomRowAction<T>` is an extra action in the row menu:
247
+
248
+ | Option | Type | Description |
249
+ | --- | --- | --- |
250
+ | `label` | `string` | Menu label. Required. |
251
+ | `icon` | `string` | Icon identifier. |
252
+ | `target` | `ActionTarget` | What the action does. Required. |
253
+ | `rule` | `RowActionRule<T>` | Per-row condition. |
254
+ | `isVisible` | `boolean` | Whether the action is shown. |
255
+ | `isDefault` | `boolean` | Make this action the row click target, ahead of `edit` / `details`. |
256
+
257
+ `CustomRowActionSerialized` is the same shape with `target: ActionTargetSerialized` and an untyped `RowActionRule`.
258
+
259
+ Where these are consumed — `TableViewOptions.rowActions`, and the archive-mode defaults — is covered in [Tables](/docs/interfaces/base/tables).
260
+
261
+ ## Guards
262
+
263
+ A guard is a function the table view calls before a mutating route runs. Throwing from it aborts the operation.
264
+
265
+ ```typescript
266
+ import type {
267
+ GuardFn,
268
+ EditGuardArgs,
269
+ TableViewGuards,
270
+ } from "@antelopejs/interface-dms/base/types/guards";
271
+ ```
272
+
273
+ The imported `GuardFn` has this signature:
274
+
275
+ ```typescript
276
+ import type { RequestContext } from "@antelopejs/interface-api";
277
+
278
+ type AfterWrite = () => void | Promise<void>;
279
+
280
+ type GuardFn<TArgs, TController = unknown> = (
281
+ this: TController,
282
+ ctx: RequestContext,
283
+ args: TArgs,
284
+ ) => void | AfterWrite | Promise<void | AfterWrite>;
285
+ ```
286
+
287
+ A mutating guard may return an `AfterWrite`: the table view runs it once the write succeeded, in the same request. It is where work comparing the row before the write (what the guard saw) with the row after belongs, such as a notification; a failure there is logged and does not fail the request, since the write is done. What a `get` guard returns is ignored.
288
+
289
+ `ctx` is the `RequestContext` from `@antelopejs/interface-api`. `this` is the data controller instance, typed as such in `TableViewGuards<T>` (`TController` is `T` there), so a guard written as a `function` reaches the properties declared with `@Model` without a cast.
290
+
291
+ | Interface | Fields |
292
+ | --- | --- |
293
+ | `EditGuardArgs<T>` | `id: string`, `body: Partial<T>`, `current: T` |
294
+ | `NewGuardArgs<T>` | `body: Partial<T>` |
295
+ | `DeleteGuardArgs` | `ids: string[]`, `values?: Record<string, unknown>` (the answer of the confirmation's `fields`; unset when the request carried no body) |
296
+ | `BulkGuardArgs` | `ids: string[]`, `values?: Record<string, unknown>` (as on `DeleteGuardArgs`) |
297
+
298
+ `TableViewGuards<T>` collects one guard per action:
299
+
300
+ | Key | Guard argument |
301
+ | --- | --- |
302
+ | `edit` | `EditGuardArgs<T>` |
303
+ | `delete` | `DeleteGuardArgs` |
304
+ | `archive` | `BulkGuardArgs` |
305
+ | `restore` | `BulkGuardArgs` |
306
+ | `new` | `NewGuardArgs<T>` |
307
+
308
+ `T` is the data controller instance `TableView()` infers from its controller; it defaults to `Record<string, unknown>` on every one of them. How guards are attached and when each one fires is documented in [Tables](/docs/interfaces/base/tables).
309
+
310
+ ## Form and Field Types
311
+
312
+ ### FormMode
313
+
314
+ `FormMode` is the enum `new`, `edit`, `view`. It selects which variant of a form the table view generates and which readonly behaviour applies.
315
+
316
+ ### ReadonlyBehavior
317
+
318
+ `ReadonlyBehaviorType` is an enum with `disabled`, `hidden` and `default`. `ReadonlyBehaviorConfig` gives one value per mode:
319
+
320
+ ```typescript
321
+ import { ReadonlyBehaviorType } from "@antelopejs/interface-dms/base/types";
322
+
323
+ const createdAtBehavior = {
324
+ new: ReadonlyBehaviorType.hidden,
325
+ edit: ReadonlyBehaviorType.disabled,
326
+ view: ReadonlyBehaviorType.disabled,
327
+ };
328
+ ```
329
+
330
+ `ReadonlyBehavior` is `ReadonlyBehaviorType | ReadonlyBehaviorConfig`, so a column may pass a single value that applies to every mode or a per-mode object. It is read by `ColumnOptions.readonlyBehavior`.
331
+
332
+ ### ListableConfig and FieldMetadata
333
+
334
+ `FieldMetadata` is the slice of a field's data-api metadata the table view reads when it builds a column or a form field.
335
+
336
+ | Field | Type | Description |
337
+ | --- | --- | --- |
338
+ | `mandatory` | `Set<FormMode>` | Modes in which the field is required. |
339
+ | `mode` | `AccessMode` | Access mode from `@antelopejs/interface-data-api/metadata`. |
340
+ | `listable` | `ListableConfig` | Which pluck modes include the field. |
341
+ | `sortable` | `boolean` | Whether the field can be sorted on. |
342
+
343
+ `ListableConfig` is `{ list?: boolean; select?: boolean; export?: boolean }` — one flag per pluck mode.
344
+
345
+ ### Field Values
346
+
347
+ `DefaultValue` and `FormFieldValue` are the same recursive union: `string | number | boolean | Date | null` and arrays of themselves. `DefaultValue` types `FormField.defaultValue` and `ColumnOptions.defaultValue`; `FormFieldValue` types a value held by a form field.
348
+
349
+ ### ComponentOrNoInput
350
+
351
+ ```typescript
352
+ type ComponentOrNoInput<T = undefined> = Component<T> | "noInput";
353
+ ```
354
+
355
+ The literal `"noInput"` stands for a compare mode that takes no value — `is_empty` and `is_not_empty` are registered that way, and `serializeType` substitutes the literal for their filter component. The serialized counterpart used by data types is `FilterComponent`, documented in [Data Types](/docs/interfaces/base/data-types).
356
+
357
+ ## Transport and Miscellaneous
358
+
359
+ ### HttpMethod
360
+
361
+ `HttpMethod` is an enum of `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, keyed by the lowercase name (`HttpMethod.get`, `HttpMethod.post`, …). Every component with a `fetchUrl` also accepts a `fetchUrlMethod` of this type.
362
+
363
+ ### AxeOrientation
364
+
365
+ `AxeOrientation` is the enum `horizontal` / `vertical`. `TabProps.orientation`, `BarChartProps.orientation` and `FormComponents.RadioGroupOptions.orientation` take it.
366
+
367
+ ### ExportStatus
368
+
369
+ `ExportStatus` is the enum `pending`, `completed`, `failed`. It is the status stored on an export job and returned by the export-status route — see [Helpers](/docs/interfaces/base/helpers).
370
+
371
+ ### Json Values
372
+
373
+ ```typescript
374
+ type JsonPrimitive = string | number | boolean | null;
375
+ type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
376
+ ```
377
+
378
+ `JsonValue` describes anything that survives a JSON round trip.
379
+
380
+ ### Error Helpers
381
+
382
+ ```typescript
383
+ import {
384
+ getErrorMessage,
385
+ isErrorWithMessage,
386
+ } from "@antelopejs/interface-dms/base/types";
387
+
388
+ try {
389
+ await save();
390
+ } catch (error: unknown) {
391
+ if (isErrorWithMessage(error)) {
392
+ // error is narrowed to ErrorWithMessage
393
+ }
394
+ const message = getErrorMessage(error); // "Unknown error" when there is none
395
+ }
396
+ ```
397
+
398
+ `ErrorWithMessage` is `{ message: string }`. `isErrorWithMessage` is a type guard that accepts any object carrying a `message` property. `getErrorMessage` returns that message, or the string `"Unknown error"` for anything else.
399
+
400
+ ## Next Steps
401
+
402
+ - [Forms](/docs/interfaces/base/forms) - Build a form, its fields, and its validation schema.