@antelopejs/interface-dms 0.7.1 → 0.7.2

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 (56) hide show
  1. package/CHANGELOG.md +56 -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-types.d.ts +1 -0
  7. package/dist/base/block-types.js +1 -0
  8. package/dist/base/block-types.js.map +1 -1
  9. package/dist/base/live-toggle.d.ts +2 -2
  10. package/dist/base/period-selector.d.ts +2 -2
  11. package/dist/base/table-view/schema.d.ts +4 -4
  12. package/dist/db/internal/exportJobs.model.d.ts +1 -0
  13. package/dist/db/internal/inviteResolutions.model.d.ts +1 -0
  14. package/dist/db/internal/tenantLifecycle.model.d.ts +1 -0
  15. package/dist/db/models/roles.model.d.ts +1 -0
  16. package/dist/db/models/tenantMembers.model.d.ts +1 -0
  17. package/dist/db/models/tenants.model.d.ts +1 -0
  18. package/dist/db/models/user_invites.model.d.ts +1 -0
  19. package/docs/.navigation.yml +2 -0
  20. package/docs/1.core/.navigation.yml +2 -0
  21. package/docs/1.core/1.introduction.md +169 -0
  22. package/docs/1.core/10.layout-banners.md +133 -0
  23. package/docs/1.core/2.pages-and-modules.md +647 -0
  24. package/docs/1.core/3.components.md +287 -0
  25. package/docs/1.core/4.permissions.md +279 -0
  26. package/docs/1.core/5.tenancy.md +356 -0
  27. package/docs/1.core/6.invites.md +239 -0
  28. package/docs/1.core/7.hooks.md +354 -0
  29. package/docs/1.core/8.realtime.md +147 -0
  30. package/docs/1.core/9.quick-actions.md +115 -0
  31. package/docs/2.auth/.navigation.yml +2 -0
  32. package/docs/2.auth/1.introduction.md +75 -0
  33. package/docs/2.auth/2.authentication.md +222 -0
  34. package/docs/2.auth/3.tokens-and-sessions.md +352 -0
  35. package/docs/2.auth/4.users.md +256 -0
  36. package/docs/2.auth/5.external-identities.md +122 -0
  37. package/docs/3.base/.navigation.yml +2 -0
  38. package/docs/3.base/1.introduction.md +202 -0
  39. package/docs/3.base/10.display-blocks.md +136 -0
  40. package/docs/3.base/2.types.md +402 -0
  41. package/docs/3.base/3.forms.md +349 -0
  42. package/docs/3.base/4.tables.md +608 -0
  43. package/docs/3.base/5.charts-and-widgets.md +466 -0
  44. package/docs/3.base/6.layout.md +431 -0
  45. package/docs/3.base/8.data-types.md +361 -0
  46. package/docs/3.base/9.helpers.md +414 -0
  47. package/docs/4.notifications/.navigation.yml +2 -0
  48. package/docs/4.notifications/1.introduction.md +79 -0
  49. package/docs/4.notifications/2.categories-and-subjects.md +212 -0
  50. package/docs/4.notifications/3.sending.md +283 -0
  51. package/docs/5.html-render/.navigation.yml +2 -0
  52. package/docs/5.html-render/1.introduction.md +372 -0
  53. package/docs/5.html-render/2.built-in-emails.md +107 -0
  54. package/docs/index.md +100 -0
  55. package/docs/tsconfig.json +11 -0
  56. package/package.json +24 -18
@@ -0,0 +1,372 @@
1
+ ---
2
+ title: "Introduction"
3
+ description: "Turn a named template and a props object into an HTML string, rendered by the frontend and delivered by your code — as an email, a file or a response."
4
+ ---
5
+
6
+ # Introduction
7
+
8
+ ## Overview
9
+
10
+ The `html-render` domain of `@antelopejs/interface-dms` turns a named template plus a props object into an HTML string. You declare a template once with `RegisterHtmlTemplate`, which records the name in a process-wide registry and hands back a typed reference; passing that reference to `GenerateHtml` produces the rendered HTML.
11
+
12
+ Rendering itself does not happen in the backend process. `GenerateHtml` performs an HTTP `POST` to the render endpoint configured on the DMS (`htmlRender.renderEndpoint`), authenticated with a signed service token, and returns the HTML the endpoint sends back. The backend therefore owns the template *names* and the props *types*; the markup lives with the renderer behind that endpoint.
13
+
14
+ The interface produces HTML and nothing else. Delivering that HTML — as a transactional email, as a file, as a response body — is your code's job.
15
+
16
+ Everything below is exported from a single subpath:
17
+
18
+ ```typescript
19
+ import {
20
+ GenerateHtml,
21
+ GetAllHtmlTemplates,
22
+ GetHtmlTemplate,
23
+ RegisterHtmlTemplate,
24
+ } from "@antelopejs/interface-dms/html-render";
25
+ import type {
26
+ HtmlRenderConfig,
27
+ HtmlTemplateInfo,
28
+ HtmlTemplateRef,
29
+ } from "@antelopejs/interface-dms/html-render";
30
+ ```
31
+
32
+ ## Key Features
33
+
34
+ - **Named template registry** — `RegisterHtmlTemplate` records a template name at import time, so the whole process shares one catalogue of renderable templates.
35
+ - **Documented props** — the type parameter on `RegisterHtmlTemplate` travels with the returned `HtmlTemplateRef`, recording on the reference itself which props the template expects.
36
+ - **Remote rendering over HTTP** — `GenerateHtml` calls the configured render endpoint with a signed service token and resolves to the returned HTML string.
37
+ - **Registry inspection** — `GetHtmlTemplate` and `GetAllHtmlTemplates` read back what has been registered, one name at a time or in bulk.
38
+ - **Configurable endpoint and token** — `HtmlRenderConfig` types the `htmlRender` block of the DMS module configuration: endpoint URL, signing secret, token lifetime.
39
+
40
+ ## Dependencies
41
+
42
+ This interface relies on the following AntelopeJS packages:
43
+
44
+ - [@antelopejs/interface-core](https://github.com/AntelopeJS/interface-core) - `InterfaceFunction` and `RegisteringProxy`, the primitives the interface is built from.
45
+
46
+ ## Quick Start
47
+
48
+ Register a template next to the code that uses it, then render it with props.
49
+
50
+ ```typescript
51
+ import {
52
+ GenerateHtml,
53
+ RegisterHtmlTemplate,
54
+ } from "@antelopejs/interface-dms/html-render";
55
+
56
+ interface WelcomeProps {
57
+ userName: string;
58
+ activationLink: string;
59
+ }
60
+
61
+ // Registration happens at import time; keep the ref in module scope.
62
+ const WelcomeTemplate = RegisterHtmlTemplate<WelcomeProps>("EmailWelcome");
63
+
64
+ export async function buildWelcomeHtml(
65
+ userName: string,
66
+ activationLink: string,
67
+ ): Promise<string> {
68
+ return GenerateHtml(WelcomeTemplate, { userName, activationLink });
69
+ }
70
+ ```
71
+
72
+ `GenerateHtml` needs the render endpoint to be reachable, so it belongs in request or job code — do not call it at import time.
73
+
74
+ ## RegisterHtmlTemplate
75
+
76
+ Registers a template name and returns the reference you render with.
77
+
78
+ ```typescript
79
+ function RegisterHtmlTemplate<TProps = unknown>(
80
+ name: string,
81
+ ): HtmlTemplateRef<TProps>;
82
+ ```
83
+
84
+ | Parameter | Description |
85
+ | --------- | ----------- |
86
+ | `name` | The template identifier. It is the key the registry stores the template under, and the `templateName` sent to the render endpoint. |
87
+
88
+ The type parameter `TProps` declares the props the template expects. It is carried by the returned [`HtmlTemplateRef`](#htmltemplateref) as a record of the template's shape; [`GenerateHtml`](#generatehtml) takes its `props` argument as `unknown`, so the compiler does not check the two against each other at the call site. Omitting `TProps` leaves the reference's props type as `unknown`.
89
+
90
+ ```typescript
91
+ import { RegisterHtmlTemplate } from "@antelopejs/interface-dms/html-render";
92
+
93
+ interface InvoiceProps {
94
+ invoiceNumber: string;
95
+ amount: string;
96
+ dueDate: string;
97
+ }
98
+
99
+ export const InvoiceTemplate =
100
+ RegisterHtmlTemplate<InvoiceProps>("EmailInvoice");
101
+ ```
102
+
103
+ The registry is keyed by name, so registering an existing name replaces the earlier entry. The DMS registers seven names for its own emails, listed in [Built-in Emails](/docs/interfaces/html-render/built-in-emails) — pick different names for your templates.
104
+
105
+ Call `RegisterHtmlTemplate` at module scope, in a file your module imports during startup. The returned reference is a plain object, so exporting it from that module is the usual way to share it.
106
+
107
+ The subpath also exports an `internal` namespace holding the registering proxy this function writes through. It is implementation detail — register through `RegisterHtmlTemplate`.
108
+
109
+ ## GenerateHtml
110
+
111
+ Renders a registered template and resolves to the resulting HTML.
112
+
113
+ ```typescript
114
+ const GenerateHtml: (
115
+ template: HtmlTemplateRef<unknown>,
116
+ props: unknown,
117
+ ) => Promise<string>;
118
+ ```
119
+
120
+ | Parameter | Description |
121
+ | --------- | ----------- |
122
+ | `template` | The reference returned by `RegisterHtmlTemplate`. Pass the reference, not the name string. |
123
+ | `props` | The data the template renders with, serialised into the request body. The render endpoint expects a JSON object and rejects anything else with a `400`, so keep the props type an object shape. Typed `unknown` at this boundary, so the props type recorded on the reference is not enforced here — match it yourself. |
124
+
125
+ ```typescript
126
+ import { GenerateHtml } from "@antelopejs/interface-dms/html-render";
127
+ import { InvoiceTemplate } from "./templates";
128
+
129
+ export async function renderInvoice(invoiceNumber: string): Promise<string> {
130
+ return GenerateHtml(InvoiceTemplate, {
131
+ invoiceNumber,
132
+ amount: "240.00 EUR",
133
+ dueDate: "2026-08-15",
134
+ });
135
+ }
136
+ ```
137
+
138
+ The call `POST`s `{ templateName, props }` as JSON to `htmlRender.renderEndpoint`, with a service token signed from `htmlRender.serviceSecret` in the `x-dms-service-token` header, and reads the `html` field out of the JSON response.
139
+
140
+ `GenerateHtml` does not consult the registry before calling out: it sends whatever name the reference carries. A name the render endpoint does not know surfaces as a rejected promise, not as a missing-template check on the backend.
141
+
142
+ On a non-2xx response the promise rejects with an `Error` whose message starts with `Failed to render HTML template:`, followed by the response body's `error` string, its `statusMessage`, or `HTTP <status>`. When the endpoint is unreachable the underlying `fetch` failure propagates instead, without that prefix. Wrap the call when a failed render must not abort the surrounding operation:
143
+
144
+ ```typescript
145
+ import { Logging } from "@antelopejs/interface-core/logging";
146
+ import { GenerateHtml } from "@antelopejs/interface-dms/html-render";
147
+ import { InvoiceTemplate } from "./templates";
148
+
149
+ export async function renderInvoiceOrNull(
150
+ invoiceNumber: string,
151
+ ): Promise<string | null> {
152
+ try {
153
+ return await GenerateHtml(InvoiceTemplate, {
154
+ invoiceNumber,
155
+ amount: "240.00 EUR",
156
+ dueDate: "2026-08-15",
157
+ });
158
+ } catch (error) {
159
+ Logging.Error(`Invoice render failed: ${(error as Error).message}`);
160
+ return null;
161
+ }
162
+ }
163
+ ```
164
+
165
+ ## GetHtmlTemplate
166
+
167
+ Looks a single template up in the registry.
168
+
169
+ ```typescript
170
+ const GetHtmlTemplate: (key: string) => Promise<HtmlTemplateInfo | undefined>;
171
+ ```
172
+
173
+ | Parameter | Description |
174
+ | --------- | ----------- |
175
+ | `key` | The name the template was registered under. |
176
+
177
+ Resolves to the [`HtmlTemplateInfo`](#htmltemplateinfo) stored for that name, or to `undefined` when nothing is registered under it.
178
+
179
+ ```typescript
180
+ import { GetHtmlTemplate } from "@antelopejs/interface-dms/html-render";
181
+
182
+ export async function isTemplateRegistered(name: string): Promise<boolean> {
183
+ return (await GetHtmlTemplate(name)) !== undefined;
184
+ }
185
+ ```
186
+
187
+ ## GetAllHtmlTemplates
188
+
189
+ Resolves to every registered template, keyed by name.
190
+
191
+ ```typescript
192
+ const GetAllHtmlTemplates: () => Promise<Record<string, HtmlTemplateInfo>>;
193
+ ```
194
+
195
+ ```typescript
196
+ import { GetAllHtmlTemplates } from "@antelopejs/interface-dms/html-render";
197
+
198
+ export async function listTemplateNames(): Promise<string[]> {
199
+ return Object.keys(await GetAllHtmlTemplates());
200
+ }
201
+ ```
202
+
203
+ The result is a fresh object built from the registry, so mutating it does not change what is registered. Because registration happens as modules are imported, call this after startup rather than at import time if you want the complete set.
204
+
205
+ ## HtmlTemplateInfo
206
+
207
+ What the registry stores for each template, and what `GetHtmlTemplate` and `GetAllHtmlTemplates` resolve to.
208
+
209
+ | Field | Type | Description |
210
+ | ----- | ---- | ----------- |
211
+ | `name` | `string` | The registered template name. |
212
+
213
+ ```typescript
214
+ import type { HtmlTemplateInfo } from "@antelopejs/interface-dms/html-render";
215
+ import { GetAllHtmlTemplates } from "@antelopejs/interface-dms/html-render";
216
+
217
+ export async function describeTemplates(): Promise<string> {
218
+ const templates: Record<string, HtmlTemplateInfo> =
219
+ await GetAllHtmlTemplates();
220
+
221
+ return Object.values(templates)
222
+ .map((info) => info.name)
223
+ .join(", ");
224
+ }
225
+ ```
226
+
227
+ ## HtmlTemplateRef
228
+
229
+ The handle `RegisterHtmlTemplate` returns and `GenerateHtml` accepts.
230
+
231
+ | Field | Type | Description |
232
+ | ----- | ---- | ----------- |
233
+ | `name` | `string` | The template name sent to the render endpoint. |
234
+ | `_propsType` | `TProps \| undefined` | Type-only carrier for the props type. It is never assigned a value; do not read it. |
235
+
236
+ Annotate the reference explicitly when you collect handles in a shared module and want the props type visible in that module's declarations:
237
+
238
+ ```typescript
239
+ import type { HtmlTemplateRef } from "@antelopejs/interface-dms/html-render";
240
+ import { RegisterHtmlTemplate } from "@antelopejs/interface-dms/html-render";
241
+
242
+ interface AlertProps {
243
+ title: string;
244
+ body: string;
245
+ }
246
+
247
+ export const AlertTemplate: HtmlTemplateRef<AlertProps> =
248
+ RegisterHtmlTemplate<AlertProps>("EmailAlert");
249
+ ```
250
+
251
+ ## HtmlRenderConfig
252
+
253
+ Types the `htmlRender` block of the DMS module configuration in `antelope.config.ts`.
254
+
255
+ | Option | Type | Description |
256
+ | ------ | ---- | ----------- |
257
+ | `renderEndpoint` | `string` | Absolute URL `GenerateHtml` posts to. Default `http://localhost:3001/api/html/render`. |
258
+ | `serviceSecret` | `string` | Secret used to sign the service token that authenticates the render call. Default `"dev"` — set a strong value in production. |
259
+ | `serviceTokenLifetime` | `number` | Lifetime of that service token, in milliseconds. The frontend render server rejects a token valid for more than five minutes. Default `300000` (5 minutes). |
260
+
261
+ The DMS merges the block you supply over those defaults, so a configuration only needs the keys it changes:
262
+
263
+ ```typescript
264
+ import { defineConfig } from "@antelopejs/interface-core/config";
265
+
266
+ const renderSecret = process.env.DMS_HTML_RENDER_SECRET;
267
+ if (!renderSecret) {
268
+ throw new Error("DMS_HTML_RENDER_SECRET is not set");
269
+ }
270
+
271
+ export default defineConfig({
272
+ name: "acme-admin",
273
+ modules: {
274
+ dms: {
275
+ source: {
276
+ type: "package",
277
+ package: "@antelopejs/dms",
278
+ version: ">=0.4.3 <1.0.0",
279
+ },
280
+ config: {
281
+ htmlRender: {
282
+ renderEndpoint: "https://admin.example.com/api/html/render",
283
+ serviceSecret: renderSecret,
284
+ },
285
+ },
286
+ },
287
+ },
288
+ });
289
+ ```
290
+
291
+ The Configuration chapter of the DMS guide covers where this block sits alongside the other DMS options.
292
+
293
+ ## Email Layout
294
+
295
+ The DMS ships the frame of its own emails as `EmailLayout`, which a module's email template (registered through `dms.email.ts`) may reuse: the branding logo, a card, an optional heading and a `footer` slot below a separator.
296
+
297
+ ```vue
298
+ <script setup lang="ts">
299
+ import EmailLayout from "#dms-layout/app/emails/components/EmailLayout.vue";
300
+
301
+ defineProps<{ subject: string; preheader: string; locale: string }>();
302
+ </script>
303
+
304
+ <template>
305
+ <EmailLayout
306
+ :lang="locale"
307
+ :preheader="preheader"
308
+ :divider="false"
309
+ public-logo-only
310
+ >
311
+ <template #head>
312
+ <title>{{ subject }}</title>
313
+ </template>
314
+ <EText>Your order has shipped.</EText>
315
+ <template #footer>
316
+ <EText>Acme Inc.</EText>
317
+ </template>
318
+ </EmailLayout>
319
+ </template>
320
+ ```
321
+
322
+ | Prop | Type | Description |
323
+ | ---- | ---- | ----------- |
324
+ | `title` | `string` (optional) | Drawn as the `h1` heading. Without it the email has no heading. |
325
+ | `lang` | `string` (optional) | The `lang` of the document. Default `"en"`. |
326
+ | `preheader` | `string` (optional) | Inbox preview text, hidden in the body. |
327
+ | `divider` | `boolean` (optional) | Draws the separator above the `footer` slot. Default `true`. |
328
+ | `logoAlt` | `string` (optional) | Alternative text of the logo. Default `"Logo"`; pass `""` for a decorative logo. |
329
+ | `publicLogoOnly` | `boolean` (optional) | Leaves the logo out unless a mail client can load it (see below). Default `false`. |
330
+
331
+ The `head` slot adds elements to the document head (a `<title>`, a viewport `<meta>`).
332
+
333
+ The logo is `branding.logo.email.light` from the app config, or `branding.logo.default.light` without an email logo, made absolute against `public.dms.clientBaseUrl` when it starts with `/`. A recipient's mail client fetches it from the internet, and Gmail and Outlook refuse SVG: with `publicLogoOnly`, the layout leaves the logo out when it is an SVG, is not an `http(s)` URL, or is served from a host a mail client cannot reach (`localhost`, `*.local`, `*.internal`, a loopback, private or link-local address), rather than drawing a broken image. Set a PNG logo and a public `clientBaseUrl` for it to show. A `branding.logo.email.dark` goes through the same rules and, when it differs from the light one, is offered to the mail clients in dark mode: the logo is then drawn as `<picture><source media="(prefers-color-scheme: dark)" srcset="…"><img …></picture>`, and a client that ignores the source draws the light image. The same check is exported as `emailLogoUrl(logoPath, clientBaseUrl)` from `#dms-layout/app/emails/components/index`, for a template that draws its own header.
334
+
335
+ ## Sending Rendered HTML as Email
336
+
337
+ Rendering and sending are separate steps. Render with `GenerateHtml`, then hand the string to the standard AntelopeJS email interface, which needs an implementing module (such as `@antelopejs/nodemailer`) loaded in your project configuration.
338
+
339
+ ```typescript
340
+ import { Send } from "@antelopejs/interface-email";
341
+ import {
342
+ GenerateHtml,
343
+ RegisterHtmlTemplate,
344
+ } from "@antelopejs/interface-dms/html-render";
345
+
346
+ interface ReceiptProps {
347
+ userName: string;
348
+ orderId: string;
349
+ }
350
+
351
+ const ReceiptTemplate = RegisterHtmlTemplate<ReceiptProps>("EmailReceipt");
352
+
353
+ export async function sendReceipt(
354
+ email: string,
355
+ userName: string,
356
+ orderId: string,
357
+ ): Promise<void> {
358
+ const html = await GenerateHtml(ReceiptTemplate, { userName, orderId });
359
+
360
+ const result = await Send({
361
+ to: email,
362
+ subject: `Your receipt for order ${orderId}`,
363
+ html,
364
+ });
365
+
366
+ if (!result.success) {
367
+ throw new Error(`Failed to send receipt: ${result.error?.message}`);
368
+ }
369
+ }
370
+ ```
371
+
372
+ This is the shape the DMS uses for its own account emails, and it keeps the two failure modes apart: a rejected `GenerateHtml` means the HTML was never produced, while a `success: false` response from `Send` means it was produced but not delivered.
@@ -0,0 +1,107 @@
1
+ ---
2
+ title: "Built-in Emails"
3
+ description: "The DMS sends seven emails of its own."
4
+ ---
5
+
6
+ # Built-in Emails
7
+
8
+ ## Overview
9
+
10
+ The DMS sends seven emails of its own. Each one is a template registered through [`RegisterHtmlTemplate`](/docs/interfaces/html-render/introduction#registerhtmltemplate), rendered by the frontend server from a Vue component in `frontend-vue/layers/dms-layout/app/emails/`, and delivered through `@antelopejs/interface-email`. They share one frame, `EmailLayout`: the branding logo, a white card, a heading, the body, a separator and a footer.
11
+
12
+ | Template | Sent when | Sent to | English subject |
13
+ | -------- | --------- | ------- | ------- |
14
+ | `EmailUserValidation` | A user signs up while `auth.mustValidateEmail` is on, or asks for a new code | The new account's address | `Verify Your Email` |
15
+ | `EmailResetPassword` | A user asks for a password reset | The account's address | `Reset Your Password` |
16
+ | `EmailTwoFactor` | A user signs in with email as their second factor | The account's address | `Your verification code` |
17
+ | `EmailAdminInvite` | A member is invited to the platform or to a workspace | The invited address | `You're invited to join <workspace> on <platform>`, shorter when a name is unknown |
18
+ | `EmailChangeVerification` | A user asks to change their sign-in email | The new address | `Confirm your new sign-in email` |
19
+ | `EmailChangeNotice` | The same request | The current address | `Your sign-in email is being changed` |
20
+ | `EmailExportReady` | A data export delivered by email has finished | The user who asked for it | `Your data export is ready` |
21
+
22
+ Each email, subject included, is written in the recipient's language, English or French (see [Language](#language)).
23
+
24
+ These names are taken: pick other names for your own emails. To change what a built-in email looks like, see [Replacing a built-in email](#replacing-a-built-in-email).
25
+
26
+ ## Props
27
+
28
+ What the backend passes to each template. A template that replaces a built-in one receives the same props.
29
+
30
+ | Template | Props |
31
+ | -------- | ----- |
32
+ | `EmailUserValidation` | `userName`, `validationCode`, `expiresInHours` (from `auth.emailValidationTokenLifetime`), `expiresIn` |
33
+ | `EmailResetPassword` | `userName`, `resetCode`, `expiresInHours` (from `auth.passwordRecoverTokenLifetime`), `expiresIn` |
34
+ | `EmailTwoFactor` | `userName`, `verificationCode` |
35
+ | `EmailAdminInvite` | `email`, `signupLink`, `expiresInDays`, and when known `workspaceName`, `inviterName`, `platformName` (`meta.title`) |
36
+ | `EmailChangeVerification` | `userName`, `newEmail`, `code`, `expiresInHours` (from `auth.emailValidationTokenLifetime`), `expiresIn` |
37
+ | `EmailChangeNotice` | `userName`, `newEmail` |
38
+ | `EmailExportReady` | `userName`, `downloadLink`, `expiresInHours`, `expiresIn`, `partial`, `failedSources` (the export sources that could not be included) |
39
+
40
+ `userName` is the user's name, or their email address when they have none. `expiresInHours` is the lifetime as a number, for the template to word in the recipient's language; `expiresIn` is the same lifetime already worded in English (`24 hours`), kept for templates written before `expiresInHours`.
41
+
42
+ ## Language
43
+
44
+ Each email is written in the recipient's language: the account's `language`, or for an invitation the language it was sent in. English and French are supported; any other language falls back to English.
45
+
46
+ The body text lives in the `dms-layout` locale files (`i18n/locales/layout-*.json`), under one key per email (`emails.user_validation`, `emails.reset_password`, `emails.two_factor`, `emails.admin_invite`, `emails.email_change_verification`, `emails.email_change_notice`, `emails.export_ready`) plus the shared `emails.common`. Locale files merge in module priority order, so a module registered with a priority above the DMS's (`0`) can reword any of these keys in its own `i18n/locales/*.json`:
47
+
48
+ ```json [frontend-vue/i18n/locales/acme-en-GB.json]
49
+ {
50
+ "emails": {
51
+ "reset_password": {
52
+ "footer": "Didn't ask for this? Contact support@acme.test."
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ The subject is set by the backend, which sends it in the same language. It cannot be reworded from a module.
59
+
60
+ ## Logo
61
+
62
+ Every built-in email draws `branding.logo.email.light` from the app config, or `branding.logo.default.light` when no email logo is set (see [Email Layout](/docs/interfaces/html-render/introduction#email-layout)), with `publicLogoOnly` on. The logo shows only when a mail client can load it: a PNG or JPEG served from a public `http(s)` address. The default DMS logo is an SVG, which Gmail and Outlook refuse, so out of the box the emails carry no logo. To show yours, set an `email` logo in your layer's `app/app.config.ts`, and serve the dashboard from a public `clientBaseUrl`:
63
+
64
+ ```ts [frontend-vue/app/app.config.ts]
65
+ export default defineDmsAppConfig({
66
+ branding: {
67
+ logo: {
68
+ email: {
69
+ light: "/branding/logo-email.png",
70
+ dark: "/branding/logo-email-dark.png",
71
+ },
72
+ },
73
+ },
74
+ });
75
+ ```
76
+
77
+ The loader hands every layer's `app/app.config.ts` to the email renderer too (`@antelopejs/dms-frontend` 0.6.0 or later), merged in module priority order, so a module registered with a priority above the DMS's (`0`) wins. `dark` is optional: when it passes the same check and differs from `light`, the header becomes a `<picture>` whose dark source the mail clients that follow the reader's dark mode pick, and the others draw `light`.
78
+
79
+ ## Replacing a built-in email
80
+
81
+ The render server looks a template up by its file name, in module priority order, and renders the first match. A module registered with a `priority` above the DMS's (`0`, see [`AddFrontendModule`](/docs/interfaces/core/pages-and-modules#addfrontendmoduleoptions)) therefore replaces a built-in email by shipping a component of the same name in its `dms.email.ts`:
82
+
83
+ ```ts [frontend-vue/dms.email.ts]
84
+ export const serverEmailTemplates = import.meta.glob("./app/emails/**/*.vue");
85
+ ```
86
+
87
+ ```vue [frontend-vue/app/emails/EmailResetPassword.vue]
88
+ <script setup lang="ts">
89
+ import { useI18n } from "vue-i18n";
90
+ import EmailLayout from "#dms-layout/app/emails/components/EmailLayout.vue";
91
+ import EmailOTP from "#dms-layout/app/emails/components/EmailOTP.vue";
92
+
93
+ defineProps<{ userName: string; resetCode: string; expiresInHours: number }>();
94
+
95
+ const { t, locale } = useI18n();
96
+ </script>
97
+
98
+ <template>
99
+ <EmailLayout :title="t('emails.reset_password.title')" :lang="locale" public-logo-only>
100
+ <EText>{{ t("emails.common.greeting", { name: userName }) }}</EText>
101
+ <EText>Use this code to choose a new Acme password:</EText>
102
+ <EmailOTP :code="resetCode" :expires-in="t('emails.common.hours', expiresInHours)" />
103
+ </EmailLayout>
104
+ </template>
105
+ ```
106
+
107
+ The replacement receives the props listed in [Props](#props) and keeps the backend's subject. Do not register the name again with `RegisterHtmlTemplate`: the DMS already sends it. At equal priority the first module by id wins, so set the priority explicitly rather than relying on it.
package/docs/index.md ADDED
@@ -0,0 +1,100 @@
1
+ ---
2
+ title: "The DMS interface"
3
+ description: "One public package, @antelopejs/interface-dms, holds every contract a module uses — its subpaths, domain by domain."
4
+ ---
5
+
6
+ # The DMS interface
7
+
8
+ ## Overview
9
+
10
+ The DMS ships one public AntelopeJS interface package, `@antelopejs/interface-dms`. Everything a module needs to declare pages, build components, authenticate a request, send a notification or render an HTML template comes from that single package, through a subpath named after the domain it belongs to. Never reach the surface through the runtime package `@antelopejs/dms`.
11
+
12
+ ```json
13
+ {
14
+ "dependencies": {
15
+ "@antelopejs/interface-dms": ">=0.0.1 <1.0.0"
16
+ }
17
+ }
18
+ ```
19
+
20
+ This section is the contract reference: the exact symbol names, options and return types, grouped by domain. The DMS guide covers the same ground task by task, organised around what you are building.
21
+
22
+ Separately published extension interfaces have their own [module interface references](/docs/modules#interface-references), including Builder. This page covers `@antelopejs/interface-dms` itself.
23
+
24
+ ## One installed copy
25
+
26
+ The host and every module must resolve **the same installed copy** of `@antelopejs/interface-dms`. The package holds live state, not just types: the page, category and extension registries, the permission tree, the hook registry, the data-type and block registries and the component slot resolvers. A second copy gets its own empty registries: pages register into a tree nobody reads, permissions never reach the roles form, and hooks never fire, without any error. `@antelopejs/core` checks this before constructing modules and fails with `Incompatible interface package resolution` when a module resolves another copy.
27
+
28
+ Depend on the package with a wide range, raising the lower bound to the version you need rather than pinning an upper one, so package managers can deduplicate, and never vendor or bundle it into a module. `@antelopejs/dms`, which implements the interface, is the exception: it caps its range below the next minor, so a breaking minor never reaches an older DMS.
29
+
30
+ The runtime also hands a module a per-context view of the values it receives through an interface, so reference equality does not survive the round trip. Compare registrations on a field, never with `===`, and compare plain data rather than the views themselves: `isDeepStrictEqual` and `assert.deepStrictEqual` refuse two views that hold equal values.
31
+
32
+ ## The Domains
33
+
34
+ | Domain | What it is for | Import prefix |
35
+ | ------ | -------------- | ------------- |
36
+ | [Core](/docs/interfaces/core/introduction) | Pages, modules, components, permissions, tenancy, invites, hooks, realtime, quick actions and layout banners. | `@antelopejs/interface-dms` and its root-level subpaths |
37
+ | [Auth](/docs/interfaces/auth/introduction) | Authentication decorators, token and session handling, and the user and session tables. | `@antelopejs/interface-dms/auth` |
38
+ | [Base](/docs/interfaces/base/introduction) | The component vocabulary: forms, tables, charts, layout, trees, data types and the shared prop types. | `@antelopejs/interface-dms/base` |
39
+ | [Notifications](/docs/interfaces/notifications/introduction) | Declaring notification categories and subjects, and building and sending notifications. | `@antelopejs/interface-dms/notifications` |
40
+ | [HTML Render](/docs/interfaces/html-render/introduction) | Registering HTML templates and rendering them server-side. | `@antelopejs/interface-dms/html-render` |
41
+
42
+ Start with [Core](/docs/interfaces/core/introduction) and [Base](/docs/interfaces/base/introduction): the first declares pages and the components they carry as serializable data, the second is the concrete set of components you fill those pages with. [Auth](/docs/interfaces/auth/introduction) resolves a request to a user, which the permission and tenant guards build on. The remaining two stand on their own and are used where you need them.
43
+
44
+ ## Subpath Map
45
+
46
+ Each subpath is its own entry point: importing one does not pull the others in, and the modules that register schemas or controllers as they evaluate are deliberately kept off the root barrel. Every domain's introduction carries the detailed map of its own subpaths; this is the whole set at a glance.
47
+
48
+ | Subpath | Holds | Domain |
49
+ | ------- | ----- | ------ |
50
+ | `@antelopejs/interface-dms` | The root barrel over the core surface below | [Core](/docs/interfaces/core/introduction) |
51
+ | `/page` | Pages, categories, modules, layouts, page extensions, dynamic menus, frontend modules | [Core](/docs/interfaces/core/pages-and-modules) |
52
+ | `/component` | `Component`, `ComponentBuilder`, actions, watches | [Core](/docs/interfaces/core/components) |
53
+ | `/component-slots` | `RegisterComponentSlot`, `ComponentSlotResolver`, `SlotOptions` | [Core](/docs/interfaces/core/components#registercomponentslot) |
54
+ | `/attachments` | `SaveComponentFiles` and its request, field and result types | [Core](/docs/interfaces/core/pages-and-modules#savecomponentfiles) |
55
+ | `/permission-gate` | Shared `PermissionGate` fields for permission-filtered UI | [Base](/docs/interfaces/base/types#permissiongate) |
56
+ | `/permissions` | The permission registry and the permission computation | [Core](/docs/interfaces/core/permissions) |
57
+ | `/permissions-resolver` | Registering and running permission resolvers | [Core](/docs/interfaces/core/permissions) |
58
+ | `/guards` | `AuthUserWithPermission`, `AuthTenantOwner`, `AuthTenantMember` | [Core](/docs/interfaces/core/permissions) |
59
+ | `/constants` | Schema names and the default tenant | [Core](/docs/interfaces/core/tenancy) |
60
+ | `/db`, `/db/models`, `/db/tables` | The `Tenant`, `TenantMember`, `Role` and `UserInvite` tables and models | [Core](/docs/interfaces/core/tenancy) |
61
+ | `/request-tenant` | `getRequestTenantId` | [Core](/docs/interfaces/core/tenancy) |
62
+ | `/tenant-scoped-model` | `TenantScopedModel` | [Core](/docs/interfaces/core/tenancy) |
63
+ | `/tenant-access` | Tenant access gates | [Core](/docs/interfaces/core/tenancy) |
64
+ | `/tenant-ownership` | Membership and ownership writes | [Core](/docs/interfaces/core/tenancy) |
65
+ | `/tenant-lifecycle` | Producer admission and permanent closure before tenant deletion | [Core](/docs/interfaces/core/tenancy#tenant-lifecycle-admission) |
66
+ | `/data-controllers` | The built-in members and roles data controllers | [Core](/docs/interfaces/core/tenancy) |
67
+ | `/invites` | Inviting a user into a tenant | [Core](/docs/interfaces/core/invites) |
68
+ | `/invite-extensions` | Invite form contributions, validation, delivery and availability | [Core](/docs/interfaces/core/invites#registerinviteextension) |
69
+ | `/invite-resolution` | Durable terminal decisions and retryable invite effects | [Core](/docs/interfaces/core/invites#durable-invite-resolution) |
70
+ | `/hooks` | The `Hook` enum, handlers and dispatch | [Core](/docs/interfaces/core/hooks) |
71
+ | `/tenant-export` | The tenant export archive, contribution and manifest types | [Core](/docs/interfaces/core/hooks) |
72
+ | `/realtime` | Page topics, publishing and backend subscription | [Core](/docs/interfaces/core/realtime) |
73
+ | `/quick-actions` | Command-palette entries | [Core](/docs/interfaces/core/quick-actions) |
74
+ | `/layout-banners` | Global banners under the dashboard header | [Core](/docs/interfaces/core/layout-banners) |
75
+ | `/uploads` | Signed upload tokens for the file and image fields of a serialized component | [Core](/docs/interfaces/core/pages-and-modules) |
76
+ | `/types` | `MaybePromise`, the one shared type alias | [Core](/docs/interfaces/core/pages-and-modules) |
77
+ | `/api-base-url`, `/client-base-url` | `GetApiBaseUrl`, `GetClientBaseUrl`: the public origins of the API and the dashboard | [Core](/docs/interfaces/core/pages-and-modules#public-urls) |
78
+ | `/client-ip` | `getClientIp`, `ResolveClientIp`: a request's client address behind the trusted proxies | [Core](/docs/interfaces/core/pages-and-modules#client-address) |
79
+ | `/auth` | Authentication decorators, tokens, sessions, external identities, account mail | [Auth](/docs/interfaces/auth/introduction) |
80
+ | `/auth/db`, `/auth/db/models`, `/auth/db/tables` | The `User`, `Session` and `UserExternalIdentity` tables and models | [Auth](/docs/interfaces/auth/users) |
81
+ | `/base` | The component barrel: forms, tables, charts, cards, layout, trees | [Base](/docs/interfaces/base/introduction) |
82
+ | `/base/types` | Base props, colours, sizes, buttons, action targets, row-action rules, guards | [Base](/docs/interfaces/base/types) |
83
+ | `/base/form` | `Form`, the field and group shapes, `FormComponents`, `FormEvents`, the schema helpers | [Base](/docs/interfaces/base/forms) |
84
+ | `/base/table-view` | `TableView`, `@Column`, `TableViewRoutes`, guards, row actions | [Base](/docs/interfaces/base/tables) |
85
+ | `/base/data-types` | `DataType`, the registration decorators, `DefaultDataTypes`, `DefaultDataCompareTypes`, `StatusType` | [Base](/docs/interfaces/base/data-types) |
86
+ | `/base/…` | The per-component subpaths — `chart`, `grid`, `stack`, `tab`, `tree`, `custom`, `layouts`, `helpers/…` | [Base](/docs/interfaces/base/introduction) |
87
+ | `/notifications`, `/notifications/types` | Categories, subjects, the notification builder and its dispatch targets | [Notifications](/docs/interfaces/notifications/introduction) |
88
+ | `/html-render` | `RegisterHtmlTemplate`, `GenerateHtml` and the template reference types | [HTML Render](/docs/interfaces/html-render/introduction) |
89
+
90
+ Beyond these entry points, built files are importable through the `./*` export (`@antelopejs/interface-dms/base/form-types`). Moving or renaming a public path is a breaking change, like removing an export.
91
+
92
+ Importability does not make a declaration public. Paths under `internal/`, symbols tagged `@internal`, and `internal` namespaces are wiring between the interface and its implementation, not supported consumer APIs. This includes compatibility exports retained at old paths. Public types named by a supported signature remain part of its contract; use the documented entry points rather than calling internal proxies or raw writer helpers.
93
+
94
+ ## How to Read a Domain
95
+
96
+ Every domain folder opens with `1.introduction.md`. It gives you the overview, the key features, the AntelopeJS packages the domain depends on, one runnable quick-start example, and the list of the folder's other pages.
97
+
98
+ The numbered pages after it are topic pages, ordered so that reading them front to back builds up. Each one starts with an overview, then works through the surface task by task with a code example per section and a table for anything that takes options. Every topic page ends with a link to the next one, so you can also follow a folder straight through from its introduction.
99
+
100
+ Read a topic page on its own when you already know which surface you need.
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "..",
5
+ "composite": false,
6
+ "incremental": false,
7
+ "tsBuildInfoFile": null,
8
+ "noEmit": true
9
+ },
10
+ "include": ["../src/**/*.ts", "**/*.ts"]
11
+ }