@antelopejs/interface-dms 0.7.0 → 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 (59) hide show
  1. package/CHANGELOG.md +82 -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/form-block-schema.d.ts +24 -24
  10. package/dist/base/live-toggle.d.ts +2 -2
  11. package/dist/base/period-selector.d.ts +2 -2
  12. package/dist/base/section.d.ts +2 -2
  13. package/dist/base/tab.d.ts +4 -4
  14. package/dist/base/table-view/schema.d.ts +4 -4
  15. package/dist/db/internal/exportJobs.model.d.ts +1 -0
  16. package/dist/db/internal/inviteResolutions.model.d.ts +1 -0
  17. package/dist/db/internal/tenantLifecycle.model.d.ts +1 -0
  18. package/dist/db/models/roles.model.d.ts +1 -0
  19. package/dist/db/models/tenantMembers.model.d.ts +1 -0
  20. package/dist/db/models/tenants.model.d.ts +1 -0
  21. package/dist/db/models/user_invites.model.d.ts +1 -0
  22. package/docs/.navigation.yml +2 -0
  23. package/docs/1.core/.navigation.yml +2 -0
  24. package/docs/1.core/1.introduction.md +169 -0
  25. package/docs/1.core/10.layout-banners.md +133 -0
  26. package/docs/1.core/2.pages-and-modules.md +647 -0
  27. package/docs/1.core/3.components.md +287 -0
  28. package/docs/1.core/4.permissions.md +279 -0
  29. package/docs/1.core/5.tenancy.md +356 -0
  30. package/docs/1.core/6.invites.md +239 -0
  31. package/docs/1.core/7.hooks.md +354 -0
  32. package/docs/1.core/8.realtime.md +147 -0
  33. package/docs/1.core/9.quick-actions.md +115 -0
  34. package/docs/2.auth/.navigation.yml +2 -0
  35. package/docs/2.auth/1.introduction.md +75 -0
  36. package/docs/2.auth/2.authentication.md +222 -0
  37. package/docs/2.auth/3.tokens-and-sessions.md +352 -0
  38. package/docs/2.auth/4.users.md +256 -0
  39. package/docs/2.auth/5.external-identities.md +122 -0
  40. package/docs/3.base/.navigation.yml +2 -0
  41. package/docs/3.base/1.introduction.md +202 -0
  42. package/docs/3.base/10.display-blocks.md +136 -0
  43. package/docs/3.base/2.types.md +402 -0
  44. package/docs/3.base/3.forms.md +349 -0
  45. package/docs/3.base/4.tables.md +608 -0
  46. package/docs/3.base/5.charts-and-widgets.md +466 -0
  47. package/docs/3.base/6.layout.md +431 -0
  48. package/docs/3.base/8.data-types.md +361 -0
  49. package/docs/3.base/9.helpers.md +414 -0
  50. package/docs/4.notifications/.navigation.yml +2 -0
  51. package/docs/4.notifications/1.introduction.md +79 -0
  52. package/docs/4.notifications/2.categories-and-subjects.md +212 -0
  53. package/docs/4.notifications/3.sending.md +283 -0
  54. package/docs/5.html-render/.navigation.yml +2 -0
  55. package/docs/5.html-render/1.introduction.md +372 -0
  56. package/docs/5.html-render/2.built-in-emails.md +107 -0
  57. package/docs/index.md +100 -0
  58. package/docs/tsconfig.json +11 -0
  59. package/package.json +24 -18
@@ -0,0 +1,361 @@
1
+ ---
2
+ title: "Data Types"
3
+ description: "A data type is the single object that answers everything the DMS needs to know about one value: which input component edits it, which Zod schema validates it, which comparison modes its filter offers, and whether the…"
4
+ ---
5
+
6
+ # Data Types
7
+
8
+ ## Overview
9
+
10
+ A data type is the single object that answers everything the DMS needs to know about one value: which input component edits it, which Zod schema validates it, which comparison modes its filter offers, and whether the field it sits on needs extra decorators. `FormField.type` and `ColumnOptions.type` both take one, which is why a form field and a table column of the same type behave identically.
11
+
12
+ ```typescript
13
+ import {
14
+ CreateDataType,
15
+ DataType,
16
+ DefaultDataCompareTypes,
17
+ DefaultDataTypes,
18
+ RegisterDataType,
19
+ serializeType,
20
+ } from "@antelopejs/interface-dms/base/data-types";
21
+ ```
22
+
23
+ The `data-types` subpath re-exports the built-in catalogues, so `DefaultDataTypes`, `DefaultDataCompareTypes` and `StatusType` are all reachable from it. They also have their own modules — `data-types/default-types`, `data-types/compare-types` and `data-types/status-type` — which is how much of the DMS imports them.
24
+
25
+ Note that the data types are **not** re-exported from the `base` barrel — always import them from the `data-types` subpath.
26
+
27
+ ## Use a Data Type
28
+
29
+ Instantiate the type with its options and hand the instance to the field or the column.
30
+
31
+ ```typescript
32
+ import { Form } from "@antelopejs/interface-dms/base";
33
+ import { DefaultDataTypes } from "@antelopejs/interface-dms/base/data-types";
34
+
35
+ const form = Form({
36
+ fields: [
37
+ {
38
+ id: "name",
39
+ label: "Name",
40
+ type: new DefaultDataTypes.StringType({ maxLength: 120 }),
41
+ required: true,
42
+ },
43
+ {
44
+ id: "status",
45
+ label: "Status",
46
+ type: new DefaultDataTypes.SelectType({
47
+ items: [
48
+ { label: "Draft", value: "draft" },
49
+ { label: "Published", value: "published" },
50
+ ],
51
+ }),
52
+ },
53
+ ],
54
+ submitUrl: "/api/articles/new",
55
+ });
56
+ ```
57
+
58
+ An instance is reusable: the same object can back a form field and a table column, since it holds only its configuration.
59
+
60
+ ## The DataType Contract
61
+
62
+ ```typescript
63
+ abstract class DataType {
64
+ constructor(
65
+ readonly compareModes: Class<DataCompareMode>[] = [],
66
+ readonly defaultCompareMode?: Class<DataCompareMode>,
67
+ readonly options?: Record<string, unknown>,
68
+ );
69
+
70
+ protected abstract defaultInputComponent(): ComponentInfoSerialized;
71
+ abstract getValidation(): z.ZodType;
72
+
73
+ filter(context, proxy, key, value, mode, row): ValueProxyOrValue<boolean>;
74
+ filterComponents(): Record<string, ComponentInfoSerialized> & { default: ComponentInfoSerialized };
75
+ inputComponent(): ComponentInfoSerialized;
76
+ decorateField?(target: unknown, key: PropertyKey, descriptor?: PropertyDescriptor): void;
77
+ }
78
+ ```
79
+
80
+ | Member | Role |
81
+ | --- | --- |
82
+ | `compareModes` | Compare-mode classes this type offers in the filter dropdown. |
83
+ | `defaultCompareMode` | The mode selected when the viewer adds a filter on this field. |
84
+ | `options` | The type's own configuration, kept on the instance and readable by other code. |
85
+ | `defaultInputComponent()` | Abstract. Returns the input component the type renders. Implemented by every concrete type. |
86
+ | `getValidation()` | Abstract. Returns the Zod schema for one value of this type, before the field-level adaptation described in [Forms](/docs/interfaces/base/forms). |
87
+ | `inputComponent()` | The form input. Defaults to `defaultInputComponent()`. |
88
+ | `filterComponents()` | The filter inputs, keyed by compare-mode id with a mandatory `default`. Defaults to `{ default: defaultInputComponent() }`. |
89
+ | `filter()` | Applies a filter for one compare mode. The base implementation looks the mode up in the compare-mode registry and delegates to it; a type overrides it when a mode needs different semantics. |
90
+ | `decorateField()` | Optional. Called by the `@Column` decorator so the type can add further decorators to the field — this is how the relation types install their join. |
91
+
92
+ ### Write a Custom Type
93
+
94
+ `StatusType` is the smallest complete example: a boolean rendered as an online/offline indicator whose labels and colours are configurable.
95
+
96
+ ```typescript
97
+ import { z } from "zod";
98
+ import { FormComponents } from "@antelopejs/interface-dms/base/form";
99
+ import {
100
+ DataType,
101
+ DefaultDataCompareTypes,
102
+ RegisterDataType,
103
+ } from "@antelopejs/interface-dms/base/data-types";
104
+
105
+ @RegisterDataType("status")
106
+ export class StatusType extends DataType {
107
+ constructor(
108
+ public readonly options: {
109
+ onlineLabel?: string;
110
+ offlineLabel?: string;
111
+ onlineColor?: string;
112
+ offlineColor?: string;
113
+ } = {
114
+ onlineLabel: "$common.status.online",
115
+ offlineLabel: "$common.status.offline",
116
+ onlineColor: "primary",
117
+ offlineColor: "neutral",
118
+ },
119
+ ) {
120
+ super(
121
+ [DefaultDataCompareTypes.Is, DefaultDataCompareTypes.IsNot],
122
+ DefaultDataCompareTypes.Is,
123
+ options as Record<string, unknown>,
124
+ );
125
+ }
126
+
127
+ protected defaultInputComponent() {
128
+ const component = FormComponents.InputCheckbox();
129
+ component.options = this.options;
130
+ return component;
131
+ }
132
+
133
+ getValidation() {
134
+ return z.boolean();
135
+ }
136
+ }
137
+ ```
138
+
139
+ Three pieces make it a type: the `@RegisterDataType("status")` decorator puts it in the registry under an id, the `super()` call declares the compare modes and stores the options, and the two abstract members supply the input and the schema. `StatusType` itself is exported by the `data-types` subpath and is one of the three column types a kanban board can group by — see [Tables](/docs/interfaces/base/tables).
140
+
141
+ ## The Registry
142
+
143
+ ### RegisterDataType
144
+
145
+ ```typescript
146
+ const RegisterDataType: (id: string) => ClassDecorator<Class<DataType>>;
147
+ ```
148
+
149
+ Registers the decorated class under `id`, in both directions: `CreateDataType` looks up the class by id, and `getDataTypeId` recovers the id from an instance. An id must be unique — registering twice under the same string replaces the earlier class.
150
+
151
+ ### RegisterDataCompareMode
152
+
153
+ ```typescript
154
+ const RegisterDataCompareMode: (
155
+ id: string,
156
+ noInput?: boolean,
157
+ ) => ClassDecorator<Class<DataCompareMode>>;
158
+ ```
159
+
160
+ Registers a compare mode. The decorator instantiates the class immediately and keeps that single instance, so a compare mode is stateless. `noInput` marks a mode that needs no value from the viewer — `serializeType` replaces its filter component with the literal `"noInput"`.
161
+
162
+ `DataCompareMode` is the interface a mode implements:
163
+
164
+ ```typescript
165
+ interface DataCompareMode {
166
+ filter(
167
+ context: RequestContext,
168
+ proxy: ValueProxy<unknown>,
169
+ key: string,
170
+ value: unknown,
171
+ row: ValueProxy<Record<string, unknown>>,
172
+ ): ValueProxyOrValue<boolean>;
173
+ }
174
+ ```
175
+
176
+ `proxy` is the database value proxy for the field being filtered and `row` the proxy for the whole row, both from `@antelopejs/interface-database`, so a mode builds a database-side predicate rather than filtering in memory.
177
+
178
+ ### CreateDataType
179
+
180
+ ```typescript
181
+ function CreateDataType(id: string, options?: unknown): DataType;
182
+ ```
183
+
184
+ Instantiates a registered type by id, forwarding `options` to its constructor. It asserts on an unknown id with the message `Unknown type <id>`. Use it when the type is chosen at runtime — from configuration, or from a description produced by another module — rather than written in the source.
185
+
186
+ ### getDataTypeId
187
+
188
+ ```typescript
189
+ function getDataTypeId(instance: DataType): string | undefined;
190
+ ```
191
+
192
+ Returns the id an instance's class was registered under, or `undefined` when the class is not registered. It resolves the class from the instance's own prototype, so a subclass of a registered type returns its own id, not its parent's.
193
+
194
+ ## Serialization
195
+
196
+ ```typescript
197
+ function serializeType(instance?: DataType): DataTypeSerialized | undefined;
198
+ ```
199
+
200
+ `serializeType` is what turns a type instance into the description shipped to the frontend. It returns `undefined` when there is no instance, and also when the instance's class is not registered.
201
+
202
+ `DataTypeSerialized`:
203
+
204
+ | Field | Type | Description |
205
+ | --- | --- | --- |
206
+ | `id` | `string` | The registered id. |
207
+ | `compareModes` | `string[]` | Ids of the type's compare modes; unregistered ones are dropped. |
208
+ | `defaultCompareMode` | `string` | Id of the default mode. |
209
+ | `filterComponents` | `Record<string, FilterComponent>` | Filter inputs by mode id, from `filterComponents()`, with every `noInput` mode's entry replaced by the literal `"noInput"`. |
210
+ | `inputComponent` | `ComponentInfoSerialized` | The form input, from `inputComponent()`. |
211
+
212
+ `FilterComponent` is `ComponentInfoSerialized | "noInput"`. The value-level counterpart, `ComponentOrNoInput`, is described in [Shared Types](/docs/interfaces/base/types).
213
+
214
+ ## Compare Modes
215
+
216
+ `DefaultDataCompareTypes` holds the built-in modes. Each entry is a class; you reference the class in a type's `compareModes`, and the string id is what appears in a serialized type and in a filter query.
217
+
218
+ | Class | Id | Input |
219
+ | --- | --- | --- |
220
+ | `Is` | `is` | value |
221
+ | `IsNot` | `is_not` | value |
222
+ | `GreaterThan` | `greater_than` | value |
223
+ | `GreaterThanOrEqualTo` | `greater_than_or_equal_to` | value |
224
+ | `LessThan` | `less_than` | value |
225
+ | `LessThanOrEqualTo` | `less_than_or_equal_to` | value |
226
+ | `IsBetween` | `is_between` | two values |
227
+ | `IsEmpty` | `is_empty` | none |
228
+ | `IsNotEmpty` | `is_not_empty` | none |
229
+ | `Include` | `include` | value |
230
+ | `Exclude` | `exclude` | value |
231
+ | `Contains` | `contains` | value |
232
+ | `NotContains` | `not_contains` | value |
233
+ | `ArrayContainsString` | `array_contains_string` | value |
234
+
235
+ `IsEmpty` and `IsNotEmpty` are the two registered with `noInput`. The namespace also exports `escapeRegex(str: string): string`, the helper the substring modes use to make a viewer-supplied value safe inside a pattern.
236
+
237
+ ## The Default Types
238
+
239
+ `DefaultDataTypes` is the built-in catalogue. Every class takes its options object as its only constructor argument, and most of them accept a `fallback` string used in place of an empty value.
240
+
241
+ | Class | Id | Compare modes (default first) | Input |
242
+ | --- | --- | --- | --- |
243
+ | `StringType` | `string` | `contains`, `is`, `is_not`, `not_contains`, `is_empty`, `is_not_empty` | text or textarea |
244
+ | `NumberType` | `number` | `is`, `is_not`, `greater_than`, `greater_than_or_equal_to`, `less_than`, `less_than_or_equal_to`, `is_empty`, `is_not_empty` | number |
245
+ | `PriceType` | `price` | as `NumberType` | number |
246
+ | `PercentageType` | `percentage` | as `NumberType` | percentage |
247
+ | `DateType` | `date` | `is`, `less_than`, `greater_than`, `is_between`, `is_empty`, `is_not_empty` | date picker |
248
+ | `BooleanType` | `boolean` | `is`, `is_not`, `is_empty`, `is_not_empty` | switch |
249
+ | `SelectType` | `select` | `include`/`exclude` when `multiple`, otherwise `is`/`is_not`; plus `is_empty`, `is_not_empty` | select |
250
+ | `EmailType` | `email` | `contains`, `is`, `is_not`, `not_contains`, `is_empty`, `is_not_empty` | email |
251
+ | `UrlType` | `url` | `is`, `is_not`, `is_empty`, `is_not_empty` | text |
252
+ | `PhoneType` | `phone` | `contains`, `is`, `is_not`, `not_contains`, `is_empty`, `is_not_empty` | phone |
253
+ | `ColorType` | `color` | `is`, `is_not`, `is_empty`, `is_not_empty` | colour |
254
+ | `PasswordType` | `password` | none | password |
255
+ | `RichTextType` | `rich_text` | none | rich text |
256
+ | `StringTimeType` | `string_time` | as `NumberType` | time |
257
+ | `RelationType` | `relation` | `is`, `is_not`, `is_empty`, `is_not_empty` | relation picker |
258
+ | `CascaderRelationType` | `cascader_relation` | `is`, `is_not`, `is_empty`, `is_not_empty` | cascader |
259
+ | `AddressType` | `address` | `is_empty`, `is_not_empty` | address |
260
+ | `FileType` | `file` | none | file upload |
261
+ | `ImageType` | `image` | none | image upload |
262
+
263
+ ### Scalar Types
264
+
265
+ | Options interface | Fields |
266
+ | --- | --- |
267
+ | `StringTypeOptions` | `placeholder?`, `maxLength?`, `minLength?`, `showCount?`, `textarea?`, `rows?`, `fallback?` |
268
+ | `NumberTypeOptions` | `min?`, `max?`, `step?`, `placeholder?`, `suffix?`, `fallback?` |
269
+ | `DateTypeOptions` | `range?`, `multiple?`, `minDate?`, `maxDate?`, `fallback?` |
270
+ | `BooleanTypeOptions` | `fallback?` |
271
+ | `SelectTypeOptions` | `items: FormComponents.SelectOption[]`, `placeholder?`, `multiple?`, `deselectable?`, `display?`, `columns?` (cards per row of the `cards` display, 2 by default), `fallback?` |
272
+ | `EmailTypeOptions`, `ColorTypeOptions`, `UrlTypeOptions`, `RichTextTypeOptions` | `placeholder?`, `fallback?` |
273
+ | `PasswordTypeOptions` | `placeholder?`, `minLength?`, `confirmPassword?`, `confirmPlaceholder?`, `fallback?` |
274
+ | `PhoneTypeOptions` | `placeholder?`, `requiredPrefix?`, `fallback?` |
275
+ | `StringTimeTypeOptions` | `placeholder?`, `min?`, `max?`, `fallback?` |
276
+
277
+ Notes on the ones whose behaviour is not obvious from the options:
278
+
279
+ - `StringType` renders a textarea instead of a single-line input when `textarea` is set, and passes `rows` to it.
280
+ - `StringType` with `showCount` shows the characters typed, out of `maxLength` when set ("4 / 60"), in its input or under its textarea.
281
+ - `NumberType` hands `suffix` (an i18n key with `$`, or a literal) to its input, which writes it after the field; the value stays a number. `PriceType` inherits it; `PercentageType` keeps its own `%`.
282
+ - `DateType` overrides `filter` so `is`, `is_not` and `is_between` compare whole UTC days rather than instants, and adds a range picker as the filter component for `is_between`. Its schema accepts a `Date` or an ISO-shaped string and transforms it to a `Date`; with `multiple` the value becomes an array, and with `range` it becomes `{ start, end }`, `end` on or after `start` (a pair `[start, end]` is still read and validates to the same object).
283
+ - `BooleanType` uses a two-option select as its filter component, so a viewer picks "checked"/"unchecked" rather than typing a value.
284
+ - `SelectType` validates against the `value`s of its `items`, as an enum or an array of that enum when `multiple` is set.
285
+ - `PriceType` is a `NumberType` whose options default to `min: 0` and `step: 0.01`.
286
+ - `PercentageType` is a `NumberType` whose options default to `min: 0`, `max: 1` and `step: 0.01`; the stored value is a fraction while the input works in whole percent.
287
+ - `PhoneType` only validates the E.164 pattern when `requiredPrefix` is set; otherwise any string passes.
288
+ - `ColorType` validates a six-digit `#RRGGBB` string.
289
+ - `StringTimeType` validates a **number** — a time of day expressed as a number, bounded by `min` and `max`.
290
+
291
+ ### Relations
292
+
293
+ `RelationType<T>` points a field at another data controller. Its input is a searchable picker whose `searchUrl` is the target controller's `select` endpoint, and — unless `filterOnly` is set — its `decorateField` installs a data-api `Foreign` join so list rows carry the resolved related row instead of the raw foreign key.
294
+
295
+ ```typescript
296
+ import { DefaultDataTypes } from "@antelopejs/interface-dms/base/data-types";
297
+ import { Column } from "@antelopejs/interface-dms/base/table-view";
298
+
299
+ @Column({
300
+ name: "Customer",
301
+ type: new DefaultDataTypes.RelationType({
302
+ dataApiController: customerDataAPI,
303
+ keyMapping: { label: "name", value: "_id" },
304
+ }),
305
+ filterable: true,
306
+ })
307
+ declare customerId: string;
308
+ ```
309
+
310
+ | Option | Type | Description |
311
+ | --- | --- | --- |
312
+ | `dataApiController` | `T extends ControllerClass` | The target data controller. Required. |
313
+ | `keyMapping` | `{ label?, value?, avatar?, disabled? }` | Which of the target's fields fill each role in the picker. Keys are checked against the target's own field names. |
314
+ | `index` | `string` | Index the join reads, when it is not the primary key. |
315
+ | `placeholder` | `string` | Picker placeholder. |
316
+ | `multiple` | `boolean` | Allow more than one related row. |
317
+ | `deselectable` | `boolean` | Allow clearing the selection. |
318
+ | `filterOnly` | `boolean` | Skip the join entirely: the raw scalar id stays in the list output and the funnel filter runs equality on it. |
319
+ | `addForm` | `{ displayName?: string; description?: string }` | Title and description of the drawer the picker's "add" entry opens ("New customer"). `$`-prefixed: i18n keys. Default to "New entry". |
320
+ | `fallback` | `string` | Text used in place of an empty value. |
321
+
322
+ When the target controller exposes a `new` endpoint and has form fields, the picker also carries an inline "add" form built from the target's `new` fields, plus the permission id of the target's `add` action.
323
+
324
+ `CascaderRelationType<T>` is the same idea for a self-referencing table: the target's `select` endpoint returns a flat list, and the widget rebuilds the tree client-side. Its `keyMapping` is required and adds `parent` — the field pointing at the parent row, which must itself carry `@Select()` — alongside the required `label` and `value`. It adds `maxDepth` and `leafOnly` to the relation options, and supports the same `filterOnly` behaviour. In a table cell it shows the path of the picked node ("Electronics / Audio"); a `multiple` one shows a badge per picked node, named by its own label with its path on hover, and folds those past three into `+N`.
325
+
326
+ ### Address
327
+
328
+ `AddressType<T>` stores a structured postal address. Its `Address` shape follows ISO 19160-1 and maps directly onto the UBL `cac:PostalAddress` used by EN 16931 / Peppol BIS Billing 3.0.
329
+
330
+ | Field | Required | Description |
331
+ | --- | --- | --- |
332
+ | `streetName` | yes | Street or thoroughfare name. |
333
+ | `postalCode` | yes | Postal or ZIP code. |
334
+ | `city` | yes | City or locality. |
335
+ | `countryCode` | yes | ISO 3166-1 alpha-2 country code. |
336
+ | `houseNumber` | no | House or building number. |
337
+ | `boxNumber` | no | Box, unit or apartment number. |
338
+ | `addressLine2` | no | Supplementary delivery information. |
339
+ | `countrySubdivision` | no | Region, state or province. |
340
+
341
+ The type's options are `placeholder` (one string per address component), `autocomplete`, an optional `dataApiController` with a `keyMapping` of `label` / `value` for picking an existing address, and `fallback`. `AddressAutocomplete` configures the geocoder:
342
+
343
+ | Option | Type | Description |
344
+ | --- | --- | --- |
345
+ | `enabled` | `boolean` | Enable suggestions on the street field. Defaults to `false`. |
346
+ | `url` | `string` | Geocoder endpoint. Defaults to `"https://photon.komoot.io/api"`. |
347
+ | `lang` | `string` | Preferred result language, ISO 639-1. Falls back to the UI locale. |
348
+ | `limit` | `number` | Maximum number of suggestions. Defaults to `5`. |
349
+ | `placeholder` | `string` | Search field placeholder. |
350
+
351
+ ### Files and Images
352
+
353
+ `FileTypeOptions` has `multiple?`, `constraints?: UploadConstraints`, `path?`, `storage?` and `fallback?`. `ImageTypeOptions` has `multiple?`, `max?`, `constraints?`, `path?`, `storage?` and `resize?`, the `FormComponents.ImageResizeOptions` the input applies client-side before uploading ([Forms](/docs/interfaces/base/forms)). Both accept `attachmentField?` for a stable native field identity and `visibility?: "private" | "public"`, defaulting to private. `constraints` is the `UploadConstraints` type from `@antelopejs/interface-file-storage`; `storage` names the storage backend and `path` the prefix uploads land under.
354
+
355
+ A file value is the resource key as a string. Metadata-based validation checks configured constraints, matching `allowedMimetypes` patterns including the `type/*` form. A native field's save integration also validates upload provenance and canonical metadata before persistence. Existing unclassified legacy values may be retained, but cannot be introduced as new native attachments or read through the native metadata endpoint. A storage outage propagates as a server error rather than becoming a field validation error. An image value is an `ImageValue` — `{ key: string; alt?: string; principal?: boolean }` — and `ImageType` defaults `allowedMimetypes` to `["image/*"]`, caps the array at `max` when `multiple` is set, and rejects a list with more than one `principal` image.
356
+
357
+ Both types enable the table view's native file handling: authorized saves promote staging keys, and successful edits or deletes clean removed native references. Failed saves retain promoted objects; cleanup is not reference-counted across records. See [File storage](/docs/components/file-storage) for authorization, visibility, retries, and custom save routes, and [Helpers](/docs/interfaces/base/helpers) for comparing references within records.
358
+
359
+ ## Next Steps
360
+
361
+ - [Helpers](/docs/interfaces/base/helpers) - Search, export jobs, file references and the archive-mode defaults.