@solidev/data 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,13 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { InjectionToken, TransferState, OnInit, ElementRef, PipeTransform, NgZone, EventEmitter, OnDestroy, ApplicationRef } from '@angular/core';
3
- import { ValidatorFn, FormControl, ValidationErrors, FormGroup, UntypedFormControl, UntypedFormGroup, ControlValueAccessor } from '@angular/forms';
4
- import * as _angular_common_http from '@angular/common/http';
5
- import { HttpClient, HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
2
+ import { InjectionToken, OnInit, ElementRef, PipeTransform, NgZone, EventEmitter, OnDestroy } from '@angular/core';
3
+ import { ValidatorFn, FormControl, ValidationErrors, FormGroup, UntypedFormGroup, ControlValueAccessor, UntypedFormControl } from '@angular/forms';
6
4
  import * as rxjs from 'rxjs';
7
5
  import { Observable, ReplaySubject, Subject, Subscription } from 'rxjs';
8
6
  import { ActivatedRoute, Router, Route, RouterState, UrlSegment } from '@angular/router';
9
7
  import { CdkDragDrop } from '@angular/cdk/drag-drop';
10
8
  import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
11
- import { SwUpdate } from '@angular/service-worker';
9
+ import * as _angular_common_http from '@angular/common/http';
10
+ import { HttpClient, HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
12
11
 
13
12
  /**
14
13
  * Base URL of the REST API every {@link Collection} builds its endpoints on.
@@ -70,15 +69,13 @@ interface DataDisplayConfig {
70
69
  * `"inline"` renders the value in flow, `"dd"` renders it as a definition
71
70
  * list entry.
72
71
  */
73
- defaultMode: "inline" | "dd";
72
+ defaultMode: 'inline' | 'dd';
74
73
  /**
75
74
  * Class string per editor type, keyed by the editor names used in
76
75
  * `DISPEDIT_EDITOR_TYPES` (e.g. `"input.text"`, `"select"`). A missing key
77
76
  * simply yields no class.
78
77
  */
79
- inputClasses: {
80
- [index: string]: string;
81
- };
78
+ inputClasses: Record<string, string>;
82
79
  };
83
80
  }
84
81
  /**
@@ -113,476 +110,517 @@ declare const BootstrapDataDisplayConfig: DataDisplayConfig;
113
110
  declare const DATA_DISPLAY_CONFIG: InjectionToken<DataDisplayConfig>;
114
111
 
115
112
  /**
116
- * A single selectable option for a `select` editor or a `choice` display.
117
- *
118
- * `value` is what is written to the model field; `desc` is the human-readable
119
- * label rendered in the dropdown or in display mode. Choices come either from
120
- * the field manager (e.g. a `charField` declared with `choices`) or from the
121
- * `[choices]` input of {@link DispeditComponent}, which overrides the manager.
122
- */
123
- interface IDispEditChoice {
124
- /** Human-readable label shown to the user. */
125
- desc: string;
126
- /** Raw value written to the model field when the option is picked. */
127
- value: any;
128
- }
129
- /**
130
- * Every editor widget {@link DispeditComponent} knows how to render.
113
+ * The HTTP layer every {@link Collection} sits on: it owns the API base URL,
114
+ * decides the headers, and optionally serves GET responses out of Angular's
115
+ * `TransferState` during SSR hydration.
131
116
  *
132
- * A field manager advertises one of these through its `editorType` property,
133
- * which is how declaring the model is enough to get the right editor. The
134
- * `[editor]` input of {@link DispeditComponent} overrides that choice.
117
+ * Collections never touch `HttpClient` directly, which makes this the one place
118
+ * to hook cross-cutting request concerns. The usual reason to care about this
119
+ * class is {@link headers}: subclass it, override that method to inject an auth
120
+ * token, and provide the subclass in place of the default.
135
121
  *
136
- * - `input.text` / `input.email` / `input.password`: matching `<input>` type,
137
- * with min/max length and pattern validation feedback.
138
- * - `input.number`: numeric `<input>`, value used as-is.
139
- * - `input.decimal`: numeric `<input>` for a `decimalField`; the value is
140
- * divided by the manager's `factor` on load and multiplied back on save.
141
- * - `input.date` / `input.datetime`: native date / datetime-local `<input>`.
142
- * - `input.checkbox`: a Oui/Non button group (not an actual checkbox).
143
- * - `textarea`: multi-line `<textarea>`.
144
- * - `select`: `<select>` built from the resolved choices. Automatically used
145
- * whenever choices are available, whatever the manager advertises.
146
- * - `quill`: rich text editor (rendered by the caller's own integration).
147
- * - `fkselect`: `<data-fkselect>` typeahead, set by `ForeignKeyFieldManager`.
148
- * - `m2mselect`: `<data-m2mselect>` multi-value typeahead, set by
149
- * `ManyToManyFieldManager`.
150
- */
151
- type DISPEDIT_EDITOR_TYPES = 'input.text' | 'input.email' | 'input.number' | 'input.decimal' | 'input.datetime' | 'input.date' | 'input.checkbox' | 'input.password' | 'textarea' | 'select' | 'quill' | 'fkselect' | 'm2mselect';
152
- /**
153
- * Every read-only renderer {@link DispeditComponent} knows how to produce.
122
+ * Requires {@link DATA_API_URL}. TransferState caching applies to GET only, and
123
+ * only when {@link DATA_MAX_TRANSFERSTATE_TIME} is provided and greater than
124
+ * zero see {@link _cachedGet} for the exact conditions.
154
125
  *
155
- * A field manager advertises one of these through its `displayType` property;
156
- * the `[viewer]` input of {@link DispeditComponent} overrides it. Booleans and
157
- * null values are short-circuited before the display type is consulted, and
158
- * rendered as `OUI` / `NON` / `non défini`.
126
+ * Dependencies are resolved with `inject()`, so a subclass declares **no
127
+ * constructor**. To point one at a different API a second backend alongside
128
+ * the main one override {@link apiUrl} rather than passing a URL through
129
+ * `super()`; {@link maxTransferstateTime} is overridable the same way.
159
130
  *
160
- * - `text`: raw value interpolated as a string. The default.
161
- * - `choice`: the `desc` of the matching {@link IDispEditChoice}. Automatically
162
- * used whenever choices are available.
163
- * - `boolean`: `OUI` / `NON`.
164
- * - `decimal`: the value divided by the `decimalField` manager's `factor`.
165
- * - `fkdetails`: the `_display` of the `<field>_details` sibling property.
166
- * - `m2mdetails`: one line per item, each rendered via its `_display`.
167
- * - `datetime` / `date`: value passed through Angular's `date` pipe (`short`
168
- * and `shortDate` respectively).
169
- * - `quill`: rich text, rendered as HTML by the caller's integration.
170
- */
171
- type DISPEDIT_DISPLAY_TYPES = 'text' | 'choice' | 'boolean' | 'decimal' | 'fkdetails' | 'm2mdetails' | 'datetime' | 'date' | 'quill';
172
-
173
- /**
174
- * Options common to every field decorator. All are optional; each concrete
175
- * field type extends this with its own (`ICharFieldMetadata`, etc.).
131
+ * @example
132
+ * ```ts
133
+ * @Injectable({ providedIn: 'root' })
134
+ * export class AuthBackend extends DataBackend {
135
+ * protected override headers(headers: { [k: string]: string }) {
136
+ * return { ...super.headers(headers), Authorization: `Bearer ${token}` };
137
+ * }
138
+ * }
176
139
  *
177
- * Whatever you pass to a decorator is copied verbatim onto the field's manager
178
- * instance, so these names double as the manager's public properties.
140
+ * @Injectable({ providedIn: 'root' })
141
+ * export class SupportBackend extends DataBackend {
142
+ * public override get apiUrl(): string {
143
+ * return 'https://support.example.com/api';
144
+ * }
145
+ * }
179
146
  *
180
- * @typeParam FT the JavaScript type the field holds on the model
147
+ * providers: [
148
+ * { provide: DATA_API_URL, useValue: 'https://api.example.com' },
149
+ * { provide: DataBackend, useClass: AuthBackend },
150
+ * ];
151
+ * ```
181
152
  */
182
- interface IFieldMetadata<FT> {
183
- /**
184
- * Wire name of the field: the key used in the JSON payload. Defaults to the
185
- * decorated property name, so it only needs setting when the API name and
186
- * the TypeScript property name differ.
187
- */
188
- name?: string;
189
- /** Short hint shown next to the editor. Defaults to an empty string. */
190
- help?: string;
191
- /**
192
- * Human-readable label used for column titles, form labels and hover
193
- * details. Defaults to {@link IFieldMetadata.name} when omitted.
194
- */
195
- description?: string;
153
+ declare class DataBackend {
196
154
  /**
197
- * Marks the field as mandatory: adds `Validators.required` and forces a
198
- * value to be present on save.
199
- *
200
- * Beware: combining `required: true` with no `defaultValue` throws at class
201
- * definition time — see {@link BaseFieldManager.getDefaultValue}.
155
+ * Instantiation timestamp, used as the origin of the TransferState validity
156
+ * window (see {@link _cachedGet}) the window runs from backend creation, not
157
+ * from each entry's insertion.
202
158
  */
203
- required?: boolean;
159
+ private _created;
160
+ private _http;
161
+ private _transferState;
162
+ private _platform;
163
+ private _apiUrl;
164
+ private _maxTransferstateTime;
204
165
  /**
205
- * Field is display-only: it is excluded from generated form groups and never
206
- * pushed back to the API. Typical for server-maintained columns and for the
207
- * `*_details` half of a relation.
166
+ * Base URL every collection URL is built on, as provided via
167
+ * {@link DATA_API_URL}. Read by `Collection.getUrl()`; exposed so custom
168
+ * collections can build URLs the same way.
208
169
  */
209
- readonly?: boolean;
170
+ get apiUrl(): string;
210
171
  /**
211
- * Value used when the model has no value for this field — either a constant
212
- * or a factory `(args) => FT` evaluated on each call, which is how you get a
213
- * fresh array/date per instance instead of a shared one.
172
+ * TransferState window in ms, from {@link DATA_MAX_TRANSFERSTATE_TIME}; `0`
173
+ * disables the cache. Overridable for the same reason as {@link apiUrl}.
214
174
  */
215
- defaultValue?: FT | ((args: any) => FT) | null;
175
+ protected get maxTransferstateTime(): number;
216
176
  /**
217
- * Display ordering weight: **higher is shown first**. `id` uses `1000`.
177
+ * Issues an arbitrary request and returns the parsed JSON body.
218
178
  *
219
- * `priority: -1` is the established idiom for "deserialise this field but
220
- * never offer it in the UI" negative-priority fields are dropped from the
221
- * default column set. It is used for the id half of every relation, whose
222
- * `*_details` twin is what a human is meant to see.
179
+ * The general-purpose entry point, used by `Collection.action()` and
180
+ * `Collection.raw()`. GET requests are routed through {@link _cachedGet} and so
181
+ * may be answered from TransferState; every other method goes straight to the
182
+ * network and carries the body.
183
+ *
184
+ * @param collection collection the request belongs to; consulted for its
185
+ * `useTransferState` flag
186
+ * @param method HTTP method
187
+ * @param url absolute URL, normally from `Collection.getUrl()`
188
+ * @param body JSON body; ignored for GET
189
+ * @param params query parameters
190
+ * @param headers extra headers, passed through {@link headers}
191
+ *
192
+ * @return an observable of the response body
223
193
  */
224
- priority?: number;
225
- }
226
- /**
227
- * Runtime behaviour of a single field: one manager instance is built per
228
- * decorated property, at class definition time, and cached in the prototype's
229
- * metadata map. Everything the library does with a field — serialise it,
230
- * validate it, build its form control, pick its widget — goes through here.
231
- *
232
- * Subclass it only to add a genuinely new field type; the shipped managers
233
- * (`CharFieldManager`, `ForeignKeyFieldManager`, ...) cover the usual cases.
234
- * Override `fromJson`/`toJson` to change the wire mapping and
235
- * `getValidators()` to add constraints.
236
- */
237
- declare class BaseFieldManager<FT> implements IFieldMetadata<FT> {
238
- /** Name is mandatory in a field manager. It is given by
239
- * field decorator if not directly given in field manager. */
240
- name: string;
241
- /** Human-readable label; falls back to {@link name} when not given. */
242
- description: string;
243
- /** Input hint; normalised to `''` when not given. */
244
- help: string;
245
- /** Whether a value must be present. Adds `Validators.required`. */
246
- required: boolean;
247
- /** Whether the field is excluded from forms and never written back. */
248
- readonly: boolean;
194
+ action<T extends DataModel, RT>(collection: Collection<T>, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', url: string, { body, params, headers }?: IActionParams): Observable<RT>;
249
195
  /**
250
- * Set to `true` only by `ComputedFieldManager`. `DataModel.setFV` skips
251
- * assignment entirely for computed fields, making them permanently
252
- * read-only in the eyes of the model.
196
+ * Issues a request whose response is taken as binary rather than JSON.
197
+ *
198
+ * Backs `Collection.blob()`; use that instead of calling this directly. Never
199
+ * cached through TransferState, whatever the method.
200
+ *
201
+ * @param collection collection the request belongs to; not used by this method
202
+ * @param method HTTP method
203
+ * @param url absolute URL
204
+ * @param body JSON body
205
+ * @param params query parameters
206
+ * @param headers extra headers, passed through {@link headers}
207
+ *
208
+ * @return an observable of the response as a `Blob`
253
209
  */
254
- computed: boolean;
255
- /** Display ordering weight; higher is shown first. See {@link IFieldMetadata.priority}. */
256
- priority: number;
257
- /** Constant or factory used when the model has no value. */
258
- defaultValue?: FT | ((args: any) => FT);
210
+ blob<T extends DataModel>(collection: Collection<T>, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', url: string, { body, params, headers }?: IActionParams): Observable<Blob>;
259
211
  /**
260
- * Which editor widget `dispedit` renders for this field in edit mode.
212
+ * Full-replacement write (HTTP PUT). Provided for completeness the CRUD path
213
+ * in `Collection` uses {@link post}/{@link patch} instead, so this is only
214
+ * reached by callers that need PUT semantics explicitly.
261
215
  *
262
- * This is the library's central idea: **declaring the model configures the
263
- * UI**. Managers set this themselves from what you declared — `charField`
264
- * flips to `'select'` as soon as `choices` is present, `foreignKeyField`
265
- * uses `'fkselect'`, `manyToManyField` `'m2mselect'` — so a correct model
266
- * yields the right editor with no per-template wiring.
216
+ * @param collection collection the request belongs to; not used by this method
217
+ * @param url absolute URL
218
+ * @param body JSON body
219
+ * @param params query parameters
220
+ * @param headers extra headers, passed through {@link headers}
267
221
  */
268
- editorType: DISPEDIT_EDITOR_TYPES;
222
+ put<T extends DataModel>(collection: Collection<T>, url: string, { body, params, headers }?: IActionParams): Observable<T>;
269
223
  /**
270
- * Which read-only renderer `dispedit` uses for this field. Chosen by the
271
- * manager the same way as {@link editorType} (`'fkdetails'` for a foreign
272
- * key, `'m2mdetails'` for a m2m, `'decimal'` for `decimalField`, ...).
224
+ * Creates a record (HTTP POST). Chosen by `Collection.save()`/`update()` when
225
+ * the model has no `id`.
226
+ *
227
+ * @param collection collection the request belongs to; not used by this method
228
+ * @param url absolute URL, normally the collection's list URL
229
+ * @param body JSON body
230
+ * @param params query parameters
231
+ * @param headers extra headers, passed through {@link headers}
232
+ * @returns the created record's JSON, expected to include the assigned `id`
273
233
  */
274
- displayType: DISPEDIT_DISPLAY_TYPES;
234
+ post<T extends DataModel>(collection: Collection<T>, url: string, { body, params, headers }?: IActionParams): Observable<Partial<T>>;
275
235
  /**
276
- * Copies `params` onto the instance, fills in `name`/`description`/`help`
277
- * defaults, then eagerly resolves the default value which means a
278
- * `required` field with no `defaultValue` throws here, at decoration time.
236
+ * Partially updates a record (HTTP PATCH). Chosen by
237
+ * `Collection.save()`/`update()` when the model already has an `id`.
279
238
  *
280
- * @param params metadata as passed to the decorator
239
+ * @param collection collection the request belongs to; not used by this method
240
+ * @param url absolute URL, normally the record's detail URL
241
+ * @param body JSON body, holding only the fields to change
242
+ * @param params query parameters
243
+ * @param headers extra headers, passed through {@link headers}
281
244
  */
282
- constructor(params: IFieldMetadata<FT>);
245
+ patch<T extends DataModel>(collection: Collection<T>, url: string, { body, params, headers }?: IActionParams): Observable<Partial<T>>;
283
246
  /**
284
- * Angular validators for this field, rebuilt on each access. Subclasses
285
- * override this getter (not {@link getValidators}) to append their own —
286
- * `CharFieldManager` adds length/pattern, `IntegerFieldManager` min/max.
247
+ * Deletes a record (HTTP DELETE). Backs `Collection.delete()`.
248
+ *
249
+ * @param collection collection the request belongs to; not used by this method
250
+ * @param url absolute URL of the record
251
+ * @param params query parameters
252
+ * @param headers extra headers, passed through {@link headers}
287
253
  */
288
- get validators(): ValidatorFn[];
254
+ delete<T extends DataModel, R>(collection: Collection<T>, url: string, { params, headers }?: IActionParams): Observable<R>;
289
255
  /**
290
- * The bare property name, stripped of any `Model__` prefix: splits
291
- * {@link name} on `'__'` and returns the second segment, or the whole name
292
- * when there is no separator.
293
- */
294
- get fname(): string;
295
- /**
296
- * Builds a typed `FormControl` for this field on a given model instance,
297
- * seeded with the instance's current value and falling back to the default
298
- * value, and wired with {@link validators}.
256
+ * Performs a GET, transparently replaying the server-rendered response from
257
+ * `TransferState` when that is still allowed.
299
258
  *
300
- * Note the seed uses `||`, so falsy current values (`0`, `''`, `false`) fall
301
- * back to the default rather than being kept.
259
+ * This is what stops an SSR app from re-fetching, in the browser, everything it
260
+ * already fetched on the server to render the page. Entries are keyed on URL +
261
+ * params + resolved headers, so requests differing in any of those do not share
262
+ * a slot.
302
263
  *
303
- * @param instance model whose value seeds the control
304
- */
305
- formControl(instance: DataModel): FormControl<FT | null>;
306
- /**
307
- * Converts a raw JSON value coming from the API into the model value.
308
- * The base implementation passes the data straight through; typed managers
309
- * override it to coerce (`boolean`), parse (`date`) or instantiate
310
- * (`details`).
264
+ * A cached value is **read** only when all of these hold: the collection has
265
+ * `useTransferState`, {@link DATA_MAX_TRANSFERSTATE_TIME} is greater than zero,
266
+ * the key is present, and less than that many milliseconds have passed since
267
+ * *this backend instance was created*. That last condition makes the window a
268
+ * one-shot hydration budget rather than a per-entry TTL: once it lapses, nothing
269
+ * is served from TransferState again for the life of the backend.
311
270
  *
312
- * @param data raw value read from the JSON payload
313
- */
314
- fromJson(data: any): FT;
315
- /**
316
- * Converts the model value back into its JSON representation for save.
317
- * The base implementation passes the data straight through.
271
+ * A value is **written** only when the collection has `useTransferState`, the
272
+ * code is running on the server, and the window is greater than zero. So in the
273
+ * browser this degrades to a plain, uncached `HttpClient.get`.
318
274
  *
319
- * @param data current model value
275
+ * @param collection collection the request belongs to; consulted for `useTransferState`
276
+ * @param url absolute URL
277
+ * @param params query parameters
278
+ * @param headers extra headers, passed through {@link headers}
320
279
  */
321
- toJson(data: FT): any;
280
+ _cachedGet<T extends DataModel, RT>(collection: Collection<T>, url: string, { params, headers }?: IActionParams): Observable<RT>;
322
281
  /**
323
- * Renders the value for display. The base implementation is plain string
324
- * interpolation.
282
+ * Fetches a single record's JSON. Backs `Collection.fetch()`, and is
283
+ * TransferState-cacheable via {@link _cachedGet}.
325
284
  *
326
- * @param data value to render
327
- * @param format reserved; ignored by the base implementation
285
+ * @param collection collection the request belongs to
286
+ * @param url absolute URL of the record
287
+ * @param params query parameters
288
+ * @param headers extra headers
328
289
  */
329
- toString(data: FT, format?: string): string;
290
+ get<T extends DataModel>(collection: Collection<T>, url: string, { params, headers }?: IActionParams): Observable<Partial<T>>;
330
291
  /**
331
- * Public accessor for this field's default value, invoking the
332
- * `defaultValue` factory with `args` when one was declared.
292
+ * Fetches a list endpoint's raw JSON including the pagination envelope, which
293
+ * `Collection.list()` then unwraps. Identical to {@link get} apart from the
294
+ * declared return type; both funnel into {@link _cachedGet}.
333
295
  *
334
- * @param args passed through to a `defaultValue` function
296
+ * @param collection collection the request belongs to
297
+ * @param url absolute URL of the list endpoint
298
+ * @param params query parameters, typically the queryset's filters and paging
299
+ * @param headers extra headers
335
300
  */
336
- default(args?: any): FT | undefined;
301
+ list<T extends DataModel>(collection: Collection<T>, url: string, { params, headers }?: IActionParams): Observable<{
302
+ results: Partial<T>[];
303
+ }>;
337
304
  /**
338
- * Builds the base validator list: `Validators.required` when the field is
339
- * required, nothing otherwise. Subclasses call it and push their own.
305
+ * Resolves the headers actually sent with a request.
340
306
  *
341
- * @protected
342
- */
343
- protected getValidators(): ValidatorFn[];
344
- /**
345
- * Resolves the default value: calls `defaultValue` when it is a function,
346
- * otherwise returns it as-is.
307
+ * **The designated extension point of this class.** The base implementation
308
+ * just copies what it is given; subclasses override it to add headers that
309
+ * every request needs — most often an `Authorization` token — and then get
310
+ * provided in place of `DataBackend`. Doing it here rather than in an HTTP
311
+ * interceptor keeps the headers part of the TransferState cache key, so
312
+ * responses fetched under different credentials do not collide.
347
313
  *
348
- * **Throws** when the field is `required` and no `defaultValue` was given.
349
- * Because the constructor calls this, the error surfaces when the class is
350
- * defined (module evaluation), not when a model is instantiated — a
351
- * `required` field without a default breaks the app at import time.
314
+ * Implementations should stay pure and synchronous: this is called once per
315
+ * request, and for GETs its result feeds the cache key.
352
316
  *
353
- * @param args passed through to a `defaultValue` function
354
- * @throws Error if `required` is set and `defaultValue` is `undefined`
355
- * @protected
317
+ * @param headers per-request headers supplied by the caller
318
+ * @returns the complete header set to send
356
319
  */
357
- protected getDefaultValue(args?: any): FT | undefined;
320
+ protected headers(headers: Record<string, string>): Record<string, string>;
321
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DataBackend, never>;
322
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<DataBackend>;
358
323
  }
324
+
325
+ /** HTTP verbs this library issues. */
326
+ type IHttpMethod = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'PATCH';
359
327
  /**
360
- * What is actually kept in a model's metadata map: the options you passed to
361
- * the decorator, plus the four entries the decorator computes. This is the
362
- * shape returned by {@link getFieldsMetadata}.
328
+ * Per-request options shared by every {@link Collection} and {@link DataBackend}
329
+ * call: body, headers and query parameters.
330
+ *
331
+ * All members are optional, so `{}` is a valid "nothing special" argument.
363
332
  */
364
- interface IFieldStoredMetadata<FT> extends IFieldMetadata<FT> {
365
- /** Map key of this entry: `` `${__name}__${propertyKey}` ``. */
366
- self: string;
367
- /** Wire name; defaulted to the decorated property name. */
368
- name: string;
369
- /** Field type code, e.g. `'charField'`, `'foreignKeyField'`. */
370
- type: string;
371
- /** The live manager instance built for this field. */
372
- manager: BaseFieldManager<FT>;
333
+ interface IActionParams {
334
+ /** JSON body; ignored by GET/DELETE requests. */
335
+ body?: unknown;
336
+ /** Extra headers, merged on top of whatever the backend's `headers()` adds. */
337
+ headers?: Record<string, string>;
338
+ /** Query string parameters. */
339
+ params?: Record<string, string>;
340
+ /**
341
+ * Expected response type. Note that the backend does not currently read this:
342
+ * `DataBackend.action()` always parses JSON and `DataBackend.blob()` always
343
+ * requests a blob. Use `Collection.blob()` to get binary data.
344
+ */
345
+ responseType?: 'json' | 'blob';
373
346
  }
374
347
  /**
375
- * Shape of every field decorator returned by {@link genericDataField}: a plain
376
- * property decorator, applied for its side effect on the prototype's metadata
377
- * map.
348
+ * Shape for describing per-collection cache tuning.
349
+ *
350
+ * Currently unused by the runtime — TransferState behaviour is driven by
351
+ * `Collection.useTransferState` plus the `DATA_MAX_TRANSFERSTATE_TIME` token.
378
352
  */
379
- type FieldDecoratorFn = (target: any, propertyKey: string | symbol) => void;
380
-
353
+ interface ICollectionCacheParams {
354
+ transferState?: {
355
+ enabled: boolean;
356
+ maxTime: number;
357
+ maxCount: number;
358
+ };
359
+ }
381
360
  /**
382
- * The HTTP layer every {@link Collection} sits on: it owns the API base URL,
383
- * decides the headers, and optionally serves GET responses out of Angular's
384
- * `TransferState` during SSR hydration.
361
+ * Constructor signature of a model class, letting a collection instantiate `T`
362
+ * without knowing it this is the `model` argument passed to
363
+ * {@link Collection}'s constructor.
364
+ */
365
+ type DataModelType<T extends DataModel> = new (_coll?: Collection<T>) => T;
366
+ /**
367
+ * Binds a {@link DataModel} class to a REST endpoint and provides the CRUD
368
+ * operations over it.
385
369
  *
386
- * Collections never touch `HttpClient` directly, which makes this the one place
387
- * to hook cross-cutting request concerns. The usual reason to care about this
388
- * class is {@link headers}: subclass it, override that method to inject an auth
389
- * token, and provide the subclass in place of the default.
370
+ * A collection is the seam between models and HTTP: it knows the path, builds
371
+ * URLs, hands raw JSON to the model for deserialization, and returns typed
372
+ * instances that carry a back-reference to it (so `thing.save()` works). One
373
+ * collection per endpoint, normally declared as a root-provided service.
390
374
  *
391
- * Requires {@link DATA_API_URL}. TransferState caching applies to GET only, and
392
- * only when {@link DATA_MAX_TRANSFERSTATE_TIME} is provided and greater than
393
- * zero — see {@link _cachedGet} for the exact conditions.
375
+ * For anything involving filtering, sorting or pagination, go through
376
+ * {@link queryset} rather than {@link list}.
377
+ *
378
+ * The endpoint is assumed to follow Django REST Framework conventions:
379
+ * `/path` for the list, `/path/<id>` for a record, `/path/<id>/<action>` for
380
+ * custom actions, and a *paginated* list response (see {@link list}).
394
381
  *
395
382
  * @example
396
383
  * ```ts
397
384
  * @Injectable({ providedIn: 'root' })
398
- * export class AuthBackend extends DataBackend {
399
- * protected override headers(headers: { [k: string]: string }) {
400
- * return { ...super.headers(headers), Authorization: `Bearer ${token}` };
385
+ * export class ThingService extends Collection<Thing> {
386
+ * constructor() {
387
+ * super(inject(DataBackend), '/path/to/things', Thing);
401
388
  * }
402
389
  * }
403
390
  *
404
- * providers: [
405
- * { provide: DATA_API_URL, useValue: 'https://api.example.com' },
406
- * { provide: DataBackend, useClass: AuthBackend },
407
- * ];
391
+ * // then
392
+ * things.fetch(1).subscribe((thing) => ...);
393
+ * things.queryset().filter({ status: 'active' }).get().subscribe((list) => ...);
408
394
  * ```
409
395
  */
410
- declare class DataBackend {
411
- private _http;
412
- private _transferState;
413
- private _platform;
414
- private _apiUrl;
415
- private _maxTransferstateTime;
396
+ declare class Collection<T extends DataModel> {
397
+ protected backend: DataBackend;
398
+ protected path: string;
399
+ model: DataModelType<T>;
416
400
  /**
417
- * Instantiation timestamp, used as the origin of the TransferState validity
418
- * window (see {@link _cachedGet}) — the window runs from backend creation, not
419
- * from each entry's insertion.
401
+ * Whether GET responses for this collection may be served from / stored in
402
+ * Angular's `TransferState` during SSR hydration.
403
+ *
404
+ * On by default, but only ever takes effect when `DATA_MAX_TRANSFERSTATE_TIME`
405
+ * is provided and greater than zero. Set it to `false` on collections whose
406
+ * data is user-specific or must not be embedded in the server-rendered HTML.
420
407
  */
421
- private _created;
408
+ useTransferState: boolean;
422
409
  /**
423
- * @param _http Angular HTTP client
424
- * @param _transferState SSR state bridge, used to replay server GETs in the browser
425
- * @param _platform platform id, used to write TransferState on the server only
426
- * @param _apiUrl base API URL; required, see {@link DATA_API_URL}
427
- * @param _maxTransferstateTime TransferState window in ms; optional, `0`
428
- * (the default) disables caching entirely
410
+ * @param backend HTTP layer; supplies the API base URL and applies auth headers
411
+ * @param path endpoint path appended to the backend's `apiUrl`, leading slash
412
+ * included and trailing slash omitted (e.g. `'/path/to/things'`)
413
+ * @param model model class this collection instantiates
429
414
  */
430
- constructor(_http: HttpClient, _transferState: TransferState, _platform: any, _apiUrl: string, _maxTransferstateTime?: number);
415
+ constructor(backend: DataBackend, path: string, model: DataModelType<T>);
431
416
  /**
432
- * Base URL every collection URL is built on, as provided via
433
- * {@link DATA_API_URL}. Read by `Collection.getUrl()`; exposed so custom
434
- * collections can build URLs the same way.
417
+ * Builds a new, unsaved model instance attached to this collection.
418
+ *
419
+ * Use it to back a creation form: the returned instance has no `id`, so the
420
+ * first `save()` POSTs it. Values passed in `data` go through the field
421
+ * managers' deserializers, exactly as if they had come from the API.
422
+ *
423
+ * @param data initial field values; omit for an empty instance
424
+ *
425
+ * @example
426
+ * ```ts
427
+ * const thing = things.create({ name: 'draft' });
428
+ * thing.save().subscribe(); // POST /path/to/things
429
+ * ```
435
430
  */
436
- get apiUrl(): string;
431
+ create(data?: Partial<T>): T;
437
432
  /**
438
- * Issues an arbitrary request and returns the parsed JSON body.
439
- *
440
- * The general-purpose entry point, used by `Collection.action()` and
441
- * `Collection.raw()`. GET requests are routed through {@link _cachedGet} and so
442
- * may be answered from TransferState; every other method goes straight to the
443
- * network and carries the body.
433
+ * Turns raw API JSON into model instances attached to this collection.
444
434
  *
445
- * @param collection collection the request belongs to; consulted for its
446
- * `useTransferState` flag
447
- * @param method HTTP method
448
- * @param url absolute URL, normally from `Collection.getUrl()`
449
- * @param body JSON body; ignored for GET
450
- * @param params query parameters
451
- * @param headers extra headers, passed through {@link headers}
435
+ * Every read path funnels through here, and the attachment is the point: the
436
+ * resulting instances can `save()`/`update()`/`action()` themselves. Call it
437
+ * directly when you have JSON from somewhere other than this collection's own
438
+ * requests (a websocket push, an embedded payload, a fixture).
452
439
  *
453
- * @return an observable of the response body
440
+ * @param data one JSON object, or an array of them
454
441
  */
455
- action<T extends DataModel, RT>(collection: Collection<T>, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', url: string, { body, params, headers }?: IActionParams): Observable<RT>;
442
+ fromJson(data: Partial<T>): T;
443
+ fromJson(data: Partial<T>[]): T[];
456
444
  /**
457
- * Issues a request whose response is taken as binary rather than JSON.
445
+ * Opens a filterable, sortable, paginated view over this collection.
458
446
  *
459
- * Backs `Collection.blob()`; use that instead of calling this directly. Never
460
- * cached through TransferState, whatever the method.
447
+ * The normal way to read lists: a {@link Queryset} accumulates query state and
448
+ * exposes results/loading/meta as observables suitable for binding straight
449
+ * into a template. Each call returns an independent queryset, so a component
450
+ * showing two filtered lists of the same endpoint creates two.
461
451
  *
462
- * @param collection collection the request belongs to; not used by this method
463
- * @param method HTTP method
464
- * @param url absolute URL
465
- * @param body JSON body
452
+ * @param options queryset behaviour; see {@link IQuerysetOptions}
453
+ */
454
+ queryset(options?: IQuerysetOptions): Queryset<T>;
455
+ /**
456
+ * Retrieves a single record by primary key and returns it as a model.
457
+ *
458
+ * @param id primary key
459
+ * @param prefix inserted between the collection path and the id, for endpoints
460
+ * nested under a segment
461
+ * @param suffix appended after the id, e.g. `'/detail'`
466
462
  * @param params query parameters
467
- * @param headers extra headers, passed through {@link headers}
463
+ * @param headers extra headers
468
464
  *
469
- * @return an observable of the response as a `Blob`
465
+ * @example
466
+ * ```ts
467
+ * things.fetch(42).subscribe((thing) => ...); // GET /path/to/things/42
468
+ * ```
470
469
  */
471
- blob<T extends DataModel>(collection: Collection<T>, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', url: string, { body, params, headers }?: IActionParams): Observable<Blob>;
470
+ fetch(id: number, { prefix, suffix, params, headers, }?: {
471
+ prefix?: string;
472
+ suffix?: string;
473
+ params?: Record<string, string>;
474
+ headers?: Record<string, string>;
475
+ }): Observable<T>;
472
476
  /**
473
- * Full-replacement write (HTTP PUT). Provided for completeness the CRUD path
474
- * in `Collection` uses {@link post}/{@link patch} instead, so this is only
475
- * reached by callers that need PUT semantics explicitly.
477
+ * Calls a custom (non-CRUD) endpoint on this collection, either on one record
478
+ * or on the collection as a whole, and returns the raw JSON response.
476
479
  *
477
- * @param collection collection the request belongs to; not used by this method
478
- * @param url absolute URL
480
+ * This is the DRF `@action` convention. `DataModel.action()` delegates here;
481
+ * call this directly for collection-level actions, which have no model.
482
+ *
483
+ * @param model record to act on, targeting `/path/to/things/<id>/<action>`, or
484
+ * `null` for a collection-level action targeting `/path/to/things/<action>`
485
+ * @param method HTTP method
486
+ * @param action action name
479
487
  * @param body JSON body
480
488
  * @param params query parameters
481
- * @param headers extra headers, passed through {@link headers}
489
+ * @param headers extra headers
482
490
  */
483
- put<T extends DataModel>(collection: Collection<T>, url: string, { body, params, headers }?: IActionParams): Observable<T>;
491
+ action<RT>(model: T | null, method: IHttpMethod, action: string, { body, params, headers }?: IActionParams): Observable<RT>;
484
492
  /**
485
- * Creates a record (HTTP POST). Chosen by `Collection.save()`/`update()` when
486
- * the model has no `id`.
493
+ * Writes a whole record: POST to the list URL when the model has no `id`,
494
+ * PATCH to its detail URL when it does.
487
495
  *
488
- * @param collection collection the request belongs to; not used by this method
489
- * @param url absolute URL, normally the collection's list URL
490
- * @param body JSON body
491
- * @param params query parameters
492
- * @param headers extra headers, passed through {@link headers}
493
- * @returns the created record's JSON, expected to include the assigned `id`
496
+ * Sends `model.toJson()`, i.e. every non-readonly field. Usually reached via
497
+ * `model.save()` rather than called directly.
498
+ *
499
+ * @param model record to persist
500
+ * @param updateModel feed the server's response back into `model` (defaults to
501
+ * `true`), which is how a freshly created record gets its `id`. The returned
502
+ * observable always emits a **separate** instance built from the response
503
+ * either way.
494
504
  */
495
- post<T extends DataModel>(collection: Collection<T>, url: string, { body, params, headers }?: IActionParams): Observable<Partial<T>>;
505
+ save(model: T, { updateModel }?: {
506
+ updateModel?: boolean;
507
+ }): Observable<T>;
496
508
  /**
497
- * Partially updates a record (HTTP PATCH). Chosen by
498
- * `Collection.save()`/`update()` when the model already has an `id`.
509
+ * Writes only the named fields of a record, dispatching POST/PATCH on `id`
510
+ * exactly like {@link save}.
499
511
  *
500
- * @param collection collection the request belongs to; not used by this method
501
- * @param url absolute URL, normally the record's detail URL
502
- * @param body JSON body, holding only the fields to change
503
- * @param params query parameters
504
- * @param headers extra headers, passed through {@link headers}
512
+ * Sends `model.toJson(fields)`, which restricts the payload to `fields` and
513
+ * does **not** filter readonly ones. Usually reached via `model.update()`.
514
+ *
515
+ * @param model record to persist
516
+ * @param fields field names to send; an empty list (the default) sends every
517
+ * non-readonly field, making this equivalent to {@link save}
518
+ * @param updateModel feed the server's response back into `model` (defaults to `true`)
505
519
  */
506
- patch<T extends DataModel>(collection: Collection<T>, url: string, { body, params, headers }?: IActionParams): Observable<Partial<T>>;
520
+ update(model: T, fields?: string[], { updateModel }?: {
521
+ updateModel?: boolean;
522
+ }): Observable<T>;
507
523
  /**
508
- * Deletes a record (HTTP DELETE). Backs `Collection.delete()`.
524
+ * Deletes a record on the server. The local instance is left untouched — it is
525
+ * the caller's job to drop it from whatever list holds it.
509
526
  *
510
- * @param collection collection the request belongs to; not used by this method
511
- * @param url absolute URL of the record
527
+ * @param model record to delete; its `id` builds the URL, so an unsaved model
528
+ * would produce a request against the list URL
512
529
  * @param params query parameters
513
- * @param headers extra headers, passed through {@link headers}
530
+ * @param headers extra headers
514
531
  */
515
- delete<T extends DataModel, R>(collection: Collection<T>, url: string, { params, headers }?: IActionParams): Observable<R>;
532
+ delete(model: T, { params, headers }?: IActionParams): Observable<unknown>;
516
533
  /**
517
- * Performs a GET, transparently replaying the server-rendered response from
518
- * `TransferState` when that is still allowed.
519
- *
520
- * This is what stops an SSR app from re-fetching, in the browser, everything it
521
- * already fetched on the server to render the page. Entries are keyed on URL +
522
- * params + resolved headers, so requests differing in any of those do not share
523
- * a slot.
534
+ * Fetches a list of records in one shot, discarding pagination metadata.
524
535
  *
525
- * A cached value is **read** only when all of these hold: the collection has
526
- * `useTransferState`, {@link DATA_MAX_TRANSFERSTATE_TIME} is greater than zero,
527
- * the key is present, and less than that many milliseconds have passed since
528
- * *this backend instance was created*. That last condition makes the window a
529
- * one-shot hydration budget rather than a per-entry TTL: once it lapses, nothing
530
- * is served from TransferState again for the life of the backend.
536
+ * A shortcut for the simple cases (a dropdown's options, a small fixed set).
537
+ * When you need paging, sorting, loading state or the result count, use
538
+ * {@link queryset} instead.
531
539
  *
532
- * A value is **written** only when the collection has `useTransferState`, the
533
- * code is running on the server, and the window is greater than zero. So in the
534
- * browser this degrades to a plain, uncached `HttpClient.get`.
540
+ * **The response must be paginated**: this reads `result.results` and returns
541
+ * that array, so a DRF endpoint with pagination disabled which answers with a
542
+ * bare JSON array yields an error rather than a list. The endpoint's page size
543
+ * therefore also caps what you get back here; there is currently no way to point
544
+ * this at a different response key (see the `FIXME` in the implementation).
535
545
  *
536
- * @param collection collection the request belongs to; consulted for `useTransferState`
537
- * @param url absolute URL
538
- * @param params query parameters
539
- * @param headers extra headers, passed through {@link headers}
546
+ * @param query filter parameters
547
+ * @param prefix inserted before the suffix in the list URL
548
+ * @param suffix appended to the list URL, for sub-endpoints
549
+ * @param params extra query parameters, merged over `query`
550
+ * @param headers extra headers
540
551
  */
541
- _cachedGet<T extends DataModel, RT>(collection: Collection<T>, url: string, { params, headers }?: IActionParams): Observable<RT>;
552
+ list(query?: FilterData, { prefix, suffix, params, headers, }?: {
553
+ prefix?: string;
554
+ suffix?: string;
555
+ params?: Record<string, string>;
556
+ headers?: Record<string, string>;
557
+ }): Observable<T[]>;
542
558
  /**
543
- * Fetches a single record's JSON. Backs `Collection.fetch()`, and is
544
- * TransferState-cacheable via {@link _cachedGet}.
559
+ * Calls a collection-level endpoint and returns the response body **unparsed**,
560
+ * without turning it into models.
545
561
  *
546
- * @param collection collection the request belongs to
547
- * @param url absolute URL of the record
562
+ * The escape hatch for responses that are not a list of records: aggregates,
563
+ * stats, export summaries. It is also what {@link Queryset} uses internally,
564
+ * since a queryset needs the pagination envelope that {@link list} throws away.
565
+ *
566
+ * @param method HTTP method; defaults to GET
567
+ * @param prefix inserted in the URL as `collection_url/PREFIXSUFFIX`
568
+ * @param suffix inserted in the URL as `collection_url/PREFIXSUFFIX`
548
569
  * @param params query parameters
570
+ * @param body accepted but **not currently sent** — the implementation does not
571
+ * forward it to the backend, so non-GET calls needing a body must go through
572
+ * {@link action}
549
573
  * @param headers extra headers
550
574
  */
551
- get<T extends DataModel>(collection: Collection<T>, url: string, { params, headers }?: IActionParams): Observable<Partial<T>>;
575
+ raw<RT>({ method, prefix, suffix, params, body: _body, headers, }?: {
576
+ method?: IHttpMethod;
577
+ prefix?: string;
578
+ suffix?: string;
579
+ body?: unknown;
580
+ params?: Record<string, string>;
581
+ headers?: Record<string, string>;
582
+ }): Observable<RT>;
552
583
  /**
553
- * Fetches a list endpoint's raw JSON including the pagination envelope, which
554
- * `Collection.list()` then unwraps. Identical to {@link get} apart from the
555
- * declared return type; both funnel into {@link _cachedGet}.
584
+ * Calls a collection-level endpoint and returns the response as a `Blob`.
556
585
  *
557
- * @param collection collection the request belongs to
558
- * @param url absolute URL of the list endpoint
559
- * @param params query parameters, typically the queryset's filters and paging
586
+ * Use it for binary or file downloads (CSV/XLSX/PDF exports) where the response
587
+ * must not be parsed as JSON.
588
+ *
589
+ * @param method HTTP method; defaults to GET
590
+ * @param prefix inserted in the URL as `collection_url/PREFIXSUFFIX`
591
+ * @param suffix inserted in the URL as `collection_url/PREFIXSUFFIX`
592
+ * @param params query parameters
593
+ * @param body accepted but **not currently sent** — the implementation does not
594
+ * forward it to the backend
560
595
  * @param headers extra headers
561
596
  */
562
- list<T extends DataModel>(collection: Collection<T>, url: string, { params, headers }?: IActionParams): Observable<any>;
597
+ blob<RT>({ method, prefix, suffix, params, body: _body, headers, }?: {
598
+ method?: IHttpMethod;
599
+ prefix?: string;
600
+ suffix?: string;
601
+ body?: unknown;
602
+ params?: Record<string, string>;
603
+ headers?: Record<string, string>;
604
+ }): Observable<Blob>;
563
605
  /**
564
- * Resolves the headers actually sent with a request.
606
+ * Builds the absolute URL for this collection or one of its records.
565
607
  *
566
- * **The designated extension point of this class.** The base implementation
567
- * just copies what it is given; subclasses override it to add headers that
568
- * every request needs — most often an `Authorization` token — and then get
569
- * provided in place of `DataBackend`. Doing it here rather than in an HTTP
570
- * interceptor keeps the headers part of the TransferState cache key, so
571
- * responses fetched under different credentials do not collide.
608
+ * Every request the collection makes goes through here, so overriding it is the
609
+ * supported way to bend URL construction (versioned or nested endpoints).
572
610
  *
573
- * Implementations should stay pure and synchronous: this is called once per
574
- * request, and for GETs its result feeds the cache key.
611
+ * Layout is `apiUrl + path` for the list, `apiUrl + path + '/' + prefix + id +
612
+ * suffix` for a record, and `apiUrl + path + '/' + prefix + suffix` for the list
613
+ * with affixes. Note the separating slash is only emitted when there is
614
+ * something after it, so a plain list URL has no trailing slash.
575
615
  *
576
- * @param headers per-request headers supplied by the caller
577
- * @returns the complete header set to send
616
+ * @param id primary key for a detail URL; `undefined` or `null` yields the list URL
617
+ * @param prefix inserted just before the id (or before the suffix)
618
+ * @param suffix appended at the very end
578
619
  */
579
- protected headers(headers: {
580
- [key: string]: string;
581
- }): {
582
- [key: string]: string;
583
- };
584
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<DataBackend, [null, null, null, null, { optional: true; }]>;
585
- static ɵprov: _angular_core.ɵɵInjectableDeclaration<DataBackend>;
620
+ getUrl(id?: number | null, { prefix, suffix }?: {
621
+ prefix?: string;
622
+ suffix?: string;
623
+ }): string;
586
624
  }
587
625
 
588
626
  /**
@@ -593,16 +631,17 @@ declare class DataBackend {
593
631
  * that filter, which is what lets a form bind a cleared control straight to a
594
632
  * queryset filter.
595
633
  */
596
- interface FilterData {
597
- [index: string]: string | number | boolean | undefined | null;
598
- }
634
+ /**
635
+ * A value a filter can hold: whatever goes on the wire for that field. A scalar
636
+ * for most filters, an array for the multi-valued ones, `null` for "cleared".
637
+ */
638
+ type FilterValue = string | number | boolean | null | undefined | (string | number)[];
639
+ type FilterData = Record<string, string | number | boolean | undefined | null>;
599
640
  /**
600
641
  * Filter criteria flattened to strings, as they go on the wire. This is what
601
642
  * {@link Queryset.getQueryParams} produces.
602
643
  */
603
- interface OutFilterData {
604
- [index: string]: string;
605
- }
644
+ type OutFilterData = Record<string, string>;
606
645
  /**
607
646
  * A single ordering term: a field name, optionally prefixed with `-` to reverse
608
647
  * it (`'name'`, `'-created'`) — the DRF `ordering` convention.
@@ -819,10 +858,10 @@ declare class Queryset<T extends DataModel> {
819
858
  * request, so a paginator must call {@link get} afterwards.
820
859
  *
821
860
  * @param page 1-based page number
822
- * @param fragment currently ignored; reserved for the URL-syncing behaviour
861
+ * @param _fragment currently ignored; reserved for the URL-syncing behaviour
823
862
  * that is commented out in the implementation
824
863
  */
825
- setPage(page?: number, fragment?: string): Queryset<T>;
864
+ setPage(page?: number, _fragment?: string): Queryset<T>;
826
865
  /**
827
866
  * Stages which fields the API should return. Chainable; issues no request.
828
867
  *
@@ -875,326 +914,276 @@ declare class Queryset<T extends DataModel> {
875
914
  get(refresh?: boolean, sync?: boolean): Observable<T[]>;
876
915
  }
877
916
 
878
- /** HTTP verbs this library issues. */
879
- type IHttpMethod = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'PATCH';
880
917
  /**
881
- * Per-request options shared by every {@link Collection} and {@link DataBackend}
882
- * call: body, headers and query parameters.
918
+ * A single selectable option for a `select` editor or a `choice` display.
883
919
  *
884
- * All members are optional, so `{}` is a valid "nothing special" argument.
920
+ * `value` is what is written to the model field; `desc` is the human-readable
921
+ * label rendered in the dropdown or in display mode. Choices come either from
922
+ * the field manager (e.g. a `charField` declared with `choices`) or from the
923
+ * `[choices]` input of {@link DispeditComponent}, which overrides the manager.
885
924
  */
886
- interface IActionParams {
887
- /** JSON body; ignored by GET/DELETE requests. */
888
- body?: any;
889
- /** Extra headers, merged on top of whatever the backend's `headers()` adds. */
890
- headers?: {
891
- [index: string]: string;
892
- };
893
- /** Query string parameters. */
894
- params?: {
895
- [index: string]: string;
896
- };
897
- /**
898
- * Expected response type. Note that the backend does not currently read this:
899
- * `DataBackend.action()` always parses JSON and `DataBackend.blob()` always
900
- * requests a blob. Use `Collection.blob()` to get binary data.
901
- */
902
- responseType?: 'json' | 'blob';
925
+ interface IDispEditChoice {
926
+ /** Human-readable label shown to the user. */
927
+ desc: string;
928
+ /** Raw value written to the model field when the option is picked. */
929
+ value: FilterValue;
903
930
  }
904
931
  /**
905
- * Shape for describing per-collection cache tuning.
932
+ * Every editor widget {@link DispeditComponent} knows how to render.
906
933
  *
907
- * Currently unused by the runtime TransferState behaviour is driven by
908
- * `Collection.useTransferState` plus the `DATA_MAX_TRANSFERSTATE_TIME` token.
909
- */
910
- interface ICollectionCacheParams {
911
- transferState?: {
912
- enabled: boolean;
913
- maxTime: number;
914
- maxCount: number;
915
- };
916
- }
917
- /**
918
- * Constructor signature of a model class, letting a collection instantiate `T`
919
- * without knowing it this is the `model` argument passed to
920
- * {@link Collection}'s constructor.
934
+ * A field manager advertises one of these through its `editorType` property,
935
+ * which is how declaring the model is enough to get the right editor. The
936
+ * `[editor]` input of {@link DispeditComponent} overrides that choice.
937
+ *
938
+ * - `input.text` / `input.email` / `input.password`: matching `<input>` type,
939
+ * with min/max length and pattern validation feedback.
940
+ * - `input.number`: numeric `<input>`, value used as-is.
941
+ * - `input.decimal`: numeric `<input>` for a `decimalField`; the value is
942
+ * divided by the manager's `factor` on load and multiplied back on save.
943
+ * - `input.date` / `input.datetime`: native date / datetime-local `<input>`.
944
+ * - `input.checkbox`: a Oui/Non button group (not an actual checkbox).
945
+ * - `textarea`: multi-line `<textarea>`.
946
+ * - `select`: `<select>` built from the resolved choices. Automatically used
947
+ * whenever choices are available, whatever the manager advertises.
948
+ * - `quill`: rich text editor (rendered by the caller's own integration).
949
+ * - `fkselect`: `<data-fkselect>` typeahead, set by `ForeignKeyFieldManager`.
950
+ * - `m2mselect`: `<data-m2mselect>` multi-value typeahead, set by
951
+ * `ManyToManyFieldManager`.
921
952
  */
922
- type DataModelType<T extends DataModel> = new (_coll?: Collection<T>) => T;
953
+ type DISPEDIT_EDITOR_TYPES = 'input.text' | 'input.email' | 'input.number' | 'input.decimal' | 'input.datetime' | 'input.date' | 'input.checkbox' | 'input.password' | 'textarea' | 'select' | 'quill' | 'fkselect' | 'm2mselect';
923
954
  /**
924
- * Binds a {@link DataModel} class to a REST endpoint and provides the CRUD
925
- * operations over it.
926
- *
927
- * A collection is the seam between models and HTTP: it knows the path, builds
928
- * URLs, hands raw JSON to the model for deserialization, and returns typed
929
- * instances that carry a back-reference to it (so `thing.save()` works). One
930
- * collection per endpoint, normally declared as a root-provided service.
955
+ * Every read-only renderer {@link DispeditComponent} knows how to produce.
931
956
  *
932
- * For anything involving filtering, sorting or pagination, go through
933
- * {@link queryset} rather than {@link list}.
957
+ * A field manager advertises one of these through its `displayType` property;
958
+ * the `[viewer]` input of {@link DispeditComponent} overrides it. Booleans and
959
+ * null values are short-circuited before the display type is consulted, and
960
+ * rendered as `OUI` / `NON` / `non défini`.
934
961
  *
935
- * The endpoint is assumed to follow Django REST Framework conventions:
936
- * `/path` for the list, `/path/<id>` for a record, `/path/<id>/<action>` for
937
- * custom actions, and a *paginated* list response (see {@link list}).
962
+ * - `text`: raw value interpolated as a string. The default.
963
+ * - `choice`: the `desc` of the matching {@link IDispEditChoice}. Automatically
964
+ * used whenever choices are available.
965
+ * - `boolean`: `OUI` / `NON`.
966
+ * - `decimal`: the value divided by the `decimalField` manager's `factor`.
967
+ * - `fkdetails`: the `_display` of the `<field>_details` sibling property.
968
+ * - `m2mdetails`: one line per item, each rendered via its `_display`.
969
+ * - `datetime` / `date`: value passed through Angular's `date` pipe (`short`
970
+ * and `shortDate` respectively).
971
+ * - `quill`: rich text, rendered as HTML by the caller's integration.
972
+ */
973
+ type DISPEDIT_DISPLAY_TYPES = 'text' | 'choice' | 'boolean' | 'decimal' | 'fkdetails' | 'm2mdetails' | 'datetime' | 'date' | 'quill';
974
+
975
+ /**
976
+ * Options common to every field decorator. All are optional; each concrete
977
+ * field type extends this with its own (`ICharFieldMetadata`, etc.).
938
978
  *
939
- * @example
940
- * ```ts
941
- * @Injectable({ providedIn: 'root' })
942
- * export class ThingService extends Collection<Thing> {
943
- * constructor() {
944
- * super(inject(DataBackend), '/path/to/things', Thing);
945
- * }
946
- * }
979
+ * Whatever you pass to a decorator is copied verbatim onto the field's manager
980
+ * instance, so these names double as the manager's public properties.
947
981
  *
948
- * // then
949
- * things.fetch(1).subscribe((thing) => ...);
950
- * things.queryset().filter({ status: 'active' }).get().subscribe((list) => ...);
951
- * ```
982
+ * @typeParam FT the JavaScript type the field holds on the model
952
983
  */
953
- declare class Collection<T extends DataModel> {
954
- protected backend: DataBackend;
955
- protected path: string;
956
- model: DataModelType<T>;
984
+ interface IFieldMetadata<FT> {
957
985
  /**
958
- * Whether GET responses for this collection may be served from / stored in
959
- * Angular's `TransferState` during SSR hydration.
960
- *
961
- * On by default, but only ever takes effect when `DATA_MAX_TRANSFERSTATE_TIME`
962
- * is provided and greater than zero. Set it to `false` on collections whose
963
- * data is user-specific or must not be embedded in the server-rendered HTML.
986
+ * Wire name of the field: the key used in the JSON payload. Defaults to the
987
+ * decorated property name, so it only needs setting when the API name and
988
+ * the TypeScript property name differ.
964
989
  */
965
- useTransferState: boolean;
990
+ name?: string;
991
+ /** Short hint shown next to the editor. Defaults to an empty string. */
992
+ help?: string;
966
993
  /**
967
- * @param backend HTTP layer; supplies the API base URL and applies auth headers
968
- * @param path endpoint path appended to the backend's `apiUrl`, leading slash
969
- * included and trailing slash omitted (e.g. `'/path/to/things'`)
970
- * @param model model class this collection instantiates
994
+ * Human-readable label used for column titles, form labels and hover
995
+ * details. Defaults to {@link IFieldMetadata.name} when omitted.
971
996
  */
972
- constructor(backend: DataBackend, path: string, model: DataModelType<T>);
997
+ description?: string;
973
998
  /**
974
- * Builds a new, unsaved model instance attached to this collection.
975
- *
976
- * Use it to back a creation form: the returned instance has no `id`, so the
977
- * first `save()` POSTs it. Values passed in `data` go through the field
978
- * managers' deserializers, exactly as if they had come from the API.
979
- *
980
- * @param data initial field values; omit for an empty instance
999
+ * Marks the field as mandatory: adds `Validators.required` and forces a
1000
+ * value to be present on save.
981
1001
  *
982
- * @example
983
- * ```ts
984
- * const thing = things.create({ name: 'draft' });
985
- * thing.save().subscribe(); // POST /path/to/things
986
- * ```
1002
+ * Beware: combining `required: true` with no `defaultValue` throws at class
1003
+ * definition time — see {@link BaseFieldManager.getDefaultValue}.
987
1004
  */
988
- create(data?: Partial<T>): T;
1005
+ required?: boolean;
989
1006
  /**
990
- * Turns raw API JSON into model instances attached to this collection.
991
- *
992
- * Every read path funnels through here, and the attachment is the point: the
993
- * resulting instances can `save()`/`update()`/`action()` themselves. Call it
994
- * directly when you have JSON from somewhere other than this collection's own
995
- * requests (a websocket push, an embedded payload, a fixture).
996
- *
997
- * @param data one JSON object, or an array of them
1007
+ * Field is display-only: it is excluded from generated form groups and never
1008
+ * pushed back to the API. Typical for server-maintained columns and for the
1009
+ * `*_details` half of a relation.
998
1010
  */
999
- fromJson(data: Partial<T>): T;
1000
- fromJson(data: Partial<T>[]): T[];
1011
+ readonly?: boolean;
1001
1012
  /**
1002
- * Opens a filterable, sortable, paginated view over this collection.
1003
- *
1004
- * The normal way to read lists: a {@link Queryset} accumulates query state and
1005
- * exposes results/loading/meta as observables suitable for binding straight
1006
- * into a template. Each call returns an independent queryset, so a component
1007
- * showing two filtered lists of the same endpoint creates two.
1013
+ * Value used when the model has no value for this field — either a constant
1014
+ * or a factory `(args) => FT` evaluated on each call, which is how you get a
1015
+ * fresh array/date per instance instead of a shared one.
1016
+ */
1017
+ defaultValue?: FT | ((args: FieldDefaultArgs) => FT) | null;
1018
+ /**
1019
+ * Display ordering weight: **higher is shown first**. `id` uses `1000`.
1008
1020
  *
1009
- * @param options queryset behaviour; see {@link IQuerysetOptions}
1021
+ * `priority: -1` is the established idiom for "deserialise this field but
1022
+ * never offer it in the UI" — negative-priority fields are dropped from the
1023
+ * default column set. It is used for the id half of every relation, whose
1024
+ * `*_details` twin is what a human is meant to see.
1010
1025
  */
1011
- queryset(options?: IQuerysetOptions): Queryset<T>;
1026
+ priority?: number;
1027
+ }
1028
+ /**
1029
+ * Runtime behaviour of a single field: one manager instance is built per
1030
+ * decorated property, at class definition time, and cached in the prototype's
1031
+ * metadata map. Everything the library does with a field — serialise it,
1032
+ * validate it, build its form control, pick its widget — goes through here.
1033
+ *
1034
+ * Subclass it only to add a genuinely new field type; the shipped managers
1035
+ * (`CharFieldManager`, `ForeignKeyFieldManager`, ...) cover the usual cases.
1036
+ * Override `fromJson`/`toJson` to change the wire mapping and
1037
+ * `getValidators()` to add constraints.
1038
+ */
1039
+ declare class BaseFieldManager<FT> implements IFieldMetadata<FT> {
1040
+ /** Name is mandatory in a field manager. It is given by
1041
+ * field decorator if not directly given in field manager. */
1042
+ name: string;
1043
+ /** Human-readable label; falls back to {@link name} when not given. */
1044
+ description: string;
1045
+ /** Input hint; normalised to `''` when not given. */
1046
+ help: string;
1047
+ /** Whether a value must be present. Adds `Validators.required`. */
1048
+ required: boolean;
1049
+ /** Whether the field is excluded from forms and never written back. */
1050
+ readonly: boolean;
1012
1051
  /**
1013
- * Retrieves a single record by primary key and returns it as a model.
1052
+ * Set to `true` only by `ComputedFieldManager`. `DataModel.setFV` skips
1053
+ * assignment entirely for computed fields, making them permanently
1054
+ * read-only in the eyes of the model.
1055
+ */
1056
+ computed: boolean;
1057
+ /** Display ordering weight; higher is shown first. See {@link IFieldMetadata.priority}. */
1058
+ priority: number;
1059
+ /** Constant or factory used when the model has no value. */
1060
+ defaultValue?: FT | ((args: FieldDefaultArgs) => FT);
1061
+ /**
1062
+ * Which editor widget `dispedit` renders for this field in edit mode.
1014
1063
  *
1015
- * @param id primary key
1016
- * @param prefix inserted between the collection path and the id, for endpoints
1017
- * nested under a segment
1018
- * @param suffix appended after the id, e.g. `'/detail'`
1019
- * @param params query parameters
1020
- * @param headers extra headers
1064
+ * This is the library's central idea: **declaring the model configures the
1065
+ * UI**. Managers set this themselves from what you declared `charField`
1066
+ * flips to `'select'` as soon as `choices` is present, `foreignKeyField`
1067
+ * uses `'fkselect'`, `manyToManyField` `'m2mselect'` so a correct model
1068
+ * yields the right editor with no per-template wiring.
1069
+ */
1070
+ editorType: DISPEDIT_EDITOR_TYPES;
1071
+ /**
1072
+ * Which read-only renderer `dispedit` uses for this field. Chosen by the
1073
+ * manager the same way as {@link editorType} (`'fkdetails'` for a foreign
1074
+ * key, `'m2mdetails'` for a m2m, `'decimal'` for `decimalField`, ...).
1075
+ */
1076
+ displayType: DISPEDIT_DISPLAY_TYPES;
1077
+ /**
1078
+ * Copies `params` onto the instance, fills in `name`/`description`/`help`
1079
+ * defaults, then eagerly resolves the default value — which means a
1080
+ * `required` field with no `defaultValue` throws here, at decoration time.
1021
1081
  *
1022
- * @example
1023
- * ```ts
1024
- * things.fetch(42).subscribe((thing) => ...); // GET /path/to/things/42
1025
- * ```
1082
+ * @param params metadata as passed to the decorator
1083
+ */
1084
+ constructor(params: IFieldMetadata<FT>);
1085
+ /**
1086
+ * Angular validators for this field, rebuilt on each access. Subclasses
1087
+ * override this getter (not {@link getValidators}) to append their own —
1088
+ * `CharFieldManager` adds length/pattern, `IntegerFieldManager` min/max.
1026
1089
  */
1027
- fetch(id: number, { prefix, suffix, params, headers, }?: {
1028
- prefix?: string;
1029
- suffix?: string;
1030
- params?: {
1031
- [_index: string]: string;
1032
- };
1033
- headers?: {
1034
- [_index: string]: string;
1035
- };
1036
- }): Observable<T>;
1090
+ get validators(): ValidatorFn[];
1037
1091
  /**
1038
- * Calls a custom (non-CRUD) endpoint on this collection, either on one record
1039
- * or on the collection as a whole, and returns the raw JSON response.
1040
- *
1041
- * This is the DRF `@action` convention. `DataModel.action()` delegates here;
1042
- * call this directly for collection-level actions, which have no model.
1043
- *
1044
- * @param model record to act on, targeting `/path/to/things/<id>/<action>`, or
1045
- * `null` for a collection-level action targeting `/path/to/things/<action>`
1046
- * @param method HTTP method
1047
- * @param action action name
1048
- * @param body JSON body
1049
- * @param params query parameters
1050
- * @param headers extra headers
1092
+ * The bare property name, stripped of any `Model__` prefix: splits
1093
+ * {@link name} on `'__'` and returns the second segment, or the whole name
1094
+ * when there is no separator.
1051
1095
  */
1052
- action<RT>(model: T | null, method: IHttpMethod, action: string, { body, params, headers }?: IActionParams): Observable<RT>;
1096
+ get fname(): string;
1053
1097
  /**
1054
- * Writes a whole record: POST to the list URL when the model has no `id`,
1055
- * PATCH to its detail URL when it does.
1098
+ * Builds a typed `FormControl` for this field on a given model instance,
1099
+ * seeded with the instance's current value and falling back to the default
1100
+ * value, and wired with {@link validators}.
1056
1101
  *
1057
- * Sends `model.toJson()`, i.e. every non-readonly field. Usually reached via
1058
- * `model.save()` rather than called directly.
1102
+ * Note the seed uses `||`, so falsy current values (`0`, `''`, `false`) fall
1103
+ * back to the default rather than being kept.
1059
1104
  *
1060
- * @param model record to persist
1061
- * @param updateModel feed the server's response back into `model` (defaults to
1062
- * `true`), which is how a freshly created record gets its `id`. The returned
1063
- * observable always emits a **separate** instance built from the response
1064
- * either way.
1105
+ * @param instance model whose value seeds the control
1065
1106
  */
1066
- save(model: T, { updateModel }?: {
1067
- updateModel?: boolean;
1068
- }): Observable<T>;
1107
+ formControl(instance: DataModel): FormControl<FT | null>;
1069
1108
  /**
1070
- * Writes only the named fields of a record, dispatching POST/PATCH on `id`
1071
- * exactly like {@link save}.
1072
- *
1073
- * Sends `model.toJson(fields)`, which restricts the payload to `fields` and
1074
- * does **not** filter readonly ones. Usually reached via `model.update()`.
1109
+ * Converts a raw JSON value coming from the API into the model value.
1110
+ * The base implementation passes the data straight through; typed managers
1111
+ * override it to coerce (`boolean`), parse (`date`) or instantiate
1112
+ * (`details`).
1075
1113
  *
1076
- * @param model record to persist
1077
- * @param fields field names to send; an empty list (the default) sends every
1078
- * non-readonly field, making this equivalent to {@link save}
1079
- * @param updateModel feed the server's response back into `model` (defaults to `true`)
1114
+ * @param data raw value read from the JSON payload
1080
1115
  */
1081
- update(model: T, fields?: string[], { updateModel }?: {
1082
- updateModel?: boolean;
1083
- }): Observable<T>;
1116
+ fromJson(data: unknown): FT;
1084
1117
  /**
1085
- * Deletes a record on the server. The local instance is left untouched it is
1086
- * the caller's job to drop it from whatever list holds it.
1118
+ * Converts the model value back into its JSON representation for save.
1119
+ * The base implementation passes the data straight through.
1087
1120
  *
1088
- * @param model record to delete; its `id` builds the URL, so an unsaved model
1089
- * would produce a request against the list URL
1090
- * @param params query parameters
1091
- * @param headers extra headers
1121
+ * @param data current model value
1092
1122
  */
1093
- delete(model: T, { params, headers }?: IActionParams): Observable<any>;
1123
+ toJson(data: FT): unknown;
1094
1124
  /**
1095
- * Fetches a list of records in one shot, discarding pagination metadata.
1096
- *
1097
- * A shortcut for the simple cases (a dropdown's options, a small fixed set).
1098
- * When you need paging, sorting, loading state or the result count, use
1099
- * {@link queryset} instead.
1100
- *
1101
- * **The response must be paginated**: this reads `result.results` and returns
1102
- * that array, so a DRF endpoint with pagination disabled — which answers with a
1103
- * bare JSON array — yields an error rather than a list. The endpoint's page size
1104
- * therefore also caps what you get back here; there is currently no way to point
1105
- * this at a different response key (see the `FIXME` in the implementation).
1125
+ * Renders the value for display. The base implementation is plain string
1126
+ * interpolation.
1106
1127
  *
1107
- * @param query filter parameters
1108
- * @param prefix inserted before the suffix in the list URL
1109
- * @param suffix appended to the list URL, for sub-endpoints
1110
- * @param params extra query parameters, merged over `query`
1111
- * @param headers extra headers
1128
+ * @param data value to render
1129
+ * @param _format reserved; ignored by the base implementation
1112
1130
  */
1113
- list(query?: FilterData, { prefix, suffix, params, headers, }?: {
1114
- prefix?: string;
1115
- suffix?: string;
1116
- params?: {
1117
- [_index: string]: string;
1118
- };
1119
- headers?: {
1120
- [_index: string]: string;
1121
- };
1122
- }): Observable<T[]>;
1131
+ toString(data: FT, _format?: string): string;
1123
1132
  /**
1124
- * Calls a collection-level endpoint and returns the response body **unparsed**,
1125
- * without turning it into models.
1126
- *
1127
- * The escape hatch for responses that are not a list of records: aggregates,
1128
- * stats, export summaries. It is also what {@link Queryset} uses internally,
1129
- * since a queryset needs the pagination envelope that {@link list} throws away.
1133
+ * Public accessor for this field's default value, invoking the
1134
+ * `defaultValue` factory with `args` when one was declared.
1130
1135
  *
1131
- * @param method HTTP method; defaults to GET
1132
- * @param prefix inserted in the URL as `collection_url/PREFIXSUFFIX`
1133
- * @param suffix inserted in the URL as `collection_url/PREFIXSUFFIX`
1134
- * @param params query parameters
1135
- * @param body accepted but **not currently sent** — the implementation does not
1136
- * forward it to the backend, so non-GET calls needing a body must go through
1137
- * {@link action}
1138
- * @param headers extra headers
1136
+ * @param args passed through to a `defaultValue` function
1139
1137
  */
1140
- raw<RT>({ method, prefix, suffix, params, body, headers, }?: {
1141
- method?: IHttpMethod;
1142
- prefix?: string;
1143
- suffix?: string;
1144
- body?: any;
1145
- params?: {
1146
- [_index: string]: string;
1147
- };
1148
- headers?: {
1149
- [_index: string]: string;
1150
- };
1151
- }): Observable<RT>;
1138
+ default(args?: FieldDefaultArgs): FT | undefined;
1152
1139
  /**
1153
- * Calls a collection-level endpoint and returns the response as a `Blob`.
1154
- *
1155
- * Use it for binary or file downloads (CSV/XLSX/PDF exports) where the response
1156
- * must not be parsed as JSON.
1140
+ * Builds the base validator list: `Validators.required` when the field is
1141
+ * required, nothing otherwise. Subclasses call it and push their own.
1157
1142
  *
1158
- * @param method HTTP method; defaults to GET
1159
- * @param prefix inserted in the URL as `collection_url/PREFIXSUFFIX`
1160
- * @param suffix inserted in the URL as `collection_url/PREFIXSUFFIX`
1161
- * @param params query parameters
1162
- * @param body accepted but **not currently sent** — the implementation does not
1163
- * forward it to the backend
1164
- * @param headers extra headers
1143
+ * @protected
1165
1144
  */
1166
- blob<RT>({ method, prefix, suffix, params, body, headers, }?: {
1167
- method?: IHttpMethod;
1168
- prefix?: string;
1169
- suffix?: string;
1170
- body?: any;
1171
- params?: {
1172
- [_index: string]: string;
1173
- };
1174
- headers?: {
1175
- [_index: string]: string;
1176
- };
1177
- }): Observable<Blob>;
1145
+ protected getValidators(): ValidatorFn[];
1178
1146
  /**
1179
- * Builds the absolute URL for this collection or one of its records.
1180
- *
1181
- * Every request the collection makes goes through here, so overriding it is the
1182
- * supported way to bend URL construction (versioned or nested endpoints).
1147
+ * Resolves the default value: calls `defaultValue` when it is a function,
1148
+ * otherwise returns it as-is.
1183
1149
  *
1184
- * Layout is `apiUrl + path` for the list, `apiUrl + path + '/' + prefix + id +
1185
- * suffix` for a record, and `apiUrl + path + '/' + prefix + suffix` for the list
1186
- * with affixes. Note the separating slash is only emitted when there is
1187
- * something after it, so a plain list URL has no trailing slash.
1150
+ * **Throws** when the field is `required` and no `defaultValue` was given.
1151
+ * Because the constructor calls this, the error surfaces when the class is
1152
+ * defined (module evaluation), not when a model is instantiated a
1153
+ * `required` field without a default breaks the app at import time.
1188
1154
  *
1189
- * @param id primary key for a detail URL; `undefined` or `null` yields the list URL
1190
- * @param prefix inserted just before the id (or before the suffix)
1191
- * @param suffix appended at the very end
1155
+ * @param args passed through to a `defaultValue` function
1156
+ * @throws Error if `required` is set and `defaultValue` is `undefined`
1157
+ * @protected
1192
1158
  */
1193
- getUrl(id?: number | null, { prefix, suffix }?: {
1194
- prefix?: string;
1195
- suffix?: string;
1196
- }): string;
1159
+ protected getDefaultValue(args?: FieldDefaultArgs): FT | undefined;
1160
+ }
1161
+ /**
1162
+ * What is actually kept in a model's metadata map: the options you passed to
1163
+ * the decorator, plus the four entries the decorator computes. This is the
1164
+ * shape returned by {@link getFieldsMetadata}.
1165
+ */
1166
+ interface IFieldStoredMetadata<FT> extends IFieldMetadata<FT> {
1167
+ /** Map key of this entry: `` `${__name}__${propertyKey}` ``. */
1168
+ self: string;
1169
+ /** Wire name; defaulted to the decorated property name. */
1170
+ name: string;
1171
+ /** Field type code, e.g. `'charField'`, `'foreignKeyField'`. */
1172
+ type: string;
1173
+ /** The live manager instance built for this field. */
1174
+ manager: BaseFieldManager<FT>;
1197
1175
  }
1176
+ /**
1177
+ * Arguments handed to a `defaultValue` factory. Free-form: callers pass
1178
+ * whatever the factory needs, and the factory knows what to expect.
1179
+ */
1180
+ type FieldDefaultArgs = Record<string, unknown>;
1181
+ /**
1182
+ * Shape of every field decorator returned by {@link genericDataField}: a plain
1183
+ * property decorator, applied for its side effect on the prototype's metadata
1184
+ * map.
1185
+ */
1186
+ type FieldDecoratorFn = (target: object, propertyKey: string | symbol) => void;
1198
1187
 
1199
1188
  /**
1200
1189
  * Resolved field metadata of a model instance, keyed by field name.
@@ -1204,7 +1193,7 @@ declare class Collection<T extends DataModel> {
1204
1193
  * filters, `dispedit`) read this map to render themselves without knowing the
1205
1194
  * concrete model.
1206
1195
  */
1207
- type DataModelFields = Readonly<Map<string, IFieldStoredMetadata<any>>>;
1196
+ type DataModelFields = Readonly<Map<string, IFieldStoredMetadata<unknown>>>;
1208
1197
  /**
1209
1198
  * Base class for every model in the library: a plain class whose fields are
1210
1199
  * declared with field decorators, which turns it into something the rest of the
@@ -1335,9 +1324,7 @@ declare class DataModel {
1335
1324
  * offending field names to their `ValidationErrors`. Note that valid fields
1336
1325
  * are still applied even when others fail — this reports, it does not roll back.
1337
1326
  */
1338
- fromJson(data: {
1339
- [index: string]: any;
1340
- }, { partial, check }?: {
1327
+ fromJson(data: object, { partial, check }?: {
1341
1328
  partial?: boolean;
1342
1329
  check?: boolean;
1343
1330
  }): null | ValidationErrors;
@@ -1378,21 +1365,13 @@ declare class DataModel {
1378
1365
  * @returns an observable of the **raw response body**, not of this model, even
1379
1366
  * when `update` is `true`
1380
1367
  */
1381
- action<T, RT extends {
1382
- [index: string]: any;
1383
- }>(method: 'POST' | 'PATCH' | 'GET' | 'DELETE' | 'PUT', name: string, { body, update, updatePartial, updateCheck, params, headers, }?: {
1384
- body?: {
1385
- [_index: string]: any;
1386
- };
1368
+ action<T, RT extends object>(method: 'POST' | 'PATCH' | 'GET' | 'DELETE' | 'PUT', name: string, { body, update, updatePartial, updateCheck, params, headers, }?: {
1369
+ body?: object;
1387
1370
  update?: boolean;
1388
1371
  updatePartial?: boolean;
1389
1372
  updateCheck?: boolean;
1390
- params?: {
1391
- [_index: string]: string;
1392
- };
1393
- headers?: {
1394
- [_index: string]: string;
1395
- };
1373
+ params?: Record<string, string>;
1374
+ headers?: Record<string, string>;
1396
1375
  }): Observable<RT>;
1397
1376
  /**
1398
1377
  * Returns a field's manager — the object holding its metadata and its
@@ -1503,6 +1482,36 @@ declare class DataModel {
1503
1482
  }): Observable<this>;
1504
1483
  }
1505
1484
 
1485
+ /**
1486
+ * A model seen as the untyped property bag it also is.
1487
+ *
1488
+ * Fields are declared by decorators and reached by name at runtime — `dispedit`
1489
+ * reads `<field>_details`, filters read `filter.field` — so the data layer
1490
+ * constantly indexes models with a string the compiler cannot resolve. This is
1491
+ * the one sanctioned way to do it: it yields `unknown`, so the value has to be
1492
+ * narrowed where it is used instead of silently becoming `any` and disabling
1493
+ * type-checking for the rest of the expression.
1494
+ *
1495
+ * ```ts
1496
+ * const details = fieldValues(model)[`${field}_details`] as R[] | undefined;
1497
+ * fieldValues(model)[field] = event.id;
1498
+ * ```
1499
+ *
1500
+ * @param target model (or any object) to index by field name
1501
+ */
1502
+ declare function fieldValues(target: object): Record<string, unknown>;
1503
+ /**
1504
+ * Renders an unknown value as display text.
1505
+ *
1506
+ * `String(value)` is not enough here: values read through {@link fieldValues}
1507
+ * can be objects, and `String({})` yields `[object Object]`. Nullish becomes an
1508
+ * empty string, objects are JSON-encoded, everything else goes through
1509
+ * `String`.
1510
+ *
1511
+ * @param value value read off a model, a form or a payload
1512
+ */
1513
+ declare function asText(value: unknown): string;
1514
+
1506
1515
  /**
1507
1516
  * One column of a list: a model field (or a {@link CustomField}) together with
1508
1517
  * its display state.
@@ -1517,7 +1526,7 @@ interface ModelListField {
1517
1526
  /** The user may enable it from the field selector. */
1518
1527
  allowed: boolean;
1519
1528
  /** The field's current value/manager, `null` for custom columns. */
1520
- field: any;
1529
+ field: unknown;
1521
1530
  /** Sort key for display order, renumbered in steps of 10 on every change. */
1522
1531
  position: number;
1523
1532
  /** Ordering weight from the field metadata, higher first; `-1` means "never shown by default". */
@@ -1620,7 +1629,7 @@ declare class ModelListFields<T extends DataModel> {
1620
1629
  * @param position new sort key
1621
1630
  * @returns current enabled fields
1622
1631
  */
1623
- setPosition(fname: any, position: number): string[];
1632
+ setPosition(fname: string, position: number): string[];
1624
1633
  private _update;
1625
1634
  }
1626
1635
 
@@ -1857,19 +1866,19 @@ interface ModelListFilterParams {
1857
1866
  * Not read by the constructor. {@link ModelListFilter} initialises its value
1858
1867
  * from {@link default} instead; see the `FIXME` in the constructor body.
1859
1868
  */
1860
- value?: any;
1869
+ value?: FilterValue;
1861
1870
  /**
1862
1871
  * Free-form description used as the `title` (native tooltip) of this filter's
1863
1872
  * entry in the `data-model-list-filters-select` picker. It has no effect on
1864
1873
  * the query.
1865
1874
  */
1866
- desc?: any;
1875
+ desc?: string;
1867
1876
  /**
1868
1877
  * Initial value applied at construction time. Only some subclasses act on it
1869
1878
  * (text, select and select-multi); the others ignore it. Its expected shape
1870
1879
  * depends on the subclass.
1871
1880
  */
1872
- default?: any;
1881
+ default?: unknown;
1873
1882
  /**
1874
1883
  * CSS classes applied to the filter's container element, overriding the
1875
1884
  * `classes` input passed by the host component. Use it to give one filter a
@@ -1928,9 +1937,9 @@ declare class ModelListFilter implements ModelListFilterParams {
1928
1937
  /** Help/placeholder text. Defaults to a French string set by each subclass. */
1929
1938
  help: string;
1930
1939
  /** Tooltip text for the filter picker entry; does not affect the query. */
1931
- desc?: any;
1940
+ desc?: string;
1932
1941
  /** Initial value, applied at construction by the subclasses that support it. */
1933
- default?: any;
1942
+ default?: unknown;
1934
1943
  /** Discriminator telling {@link ModelListFiltersComponent} which widget to render. */
1935
1944
  type: FILTER_TYPE;
1936
1945
  /**
@@ -2029,7 +2038,7 @@ declare class ModelListFilter implements ModelListFilterParams {
2029
2038
  * @returns `true` when the value actually changed, `false` when it was
2030
2039
  * already set. Callers use this to avoid re-querying the list needlessly.
2031
2040
  */
2032
- set(value: any, desc: string, notify?: boolean): boolean;
2041
+ set(value: FilterValue, desc: string, notify?: boolean): boolean;
2033
2042
  /**
2034
2043
  * Removes `value` from the filter. On a multi-valued filter only that value
2035
2044
  * is dropped, which is what the chips' delete buttons call; on a
@@ -2040,7 +2049,7 @@ declare class ModelListFilter implements ModelListFilterParams {
2040
2049
  * @param notify Whether to emit on the value stream.
2041
2050
  * @returns `true` when something was actually removed.
2042
2051
  */
2043
- unset(value: any, desc: string, notify?: boolean): boolean;
2052
+ unset(value: FilterValue, desc: string, notify?: boolean): boolean;
2044
2053
  /**
2045
2054
  * Adds `value` if absent, removes it if present. Convenient for checkbox-like
2046
2055
  * widgets where the same gesture selects and deselects.
@@ -2050,7 +2059,7 @@ declare class ModelListFilter implements ModelListFilterParams {
2050
2059
  * @param notify Whether to emit on the value stream.
2051
2060
  * @returns Always `true`.
2052
2061
  */
2053
- toggle(value: any, desc: string, notify?: boolean): boolean;
2062
+ toggle(value: FilterValue, desc: string, notify?: boolean): boolean;
2054
2063
  /**
2055
2064
  * The current single value, stringified, as sent to the server. Only
2056
2065
  * meaningful for single-valued filters.
@@ -2205,9 +2214,7 @@ interface CustomField {
2205
2214
  * This is scoping/context (the current tenant, the parent object of a nested
2206
2215
  * list), not user-facing filtering — see {@link ModelListParams.filter}.
2207
2216
  */
2208
- interface FilterDefaults {
2209
- [index: string]: string | number | undefined;
2210
- }
2217
+ type FilterDefaults = Record<string, string | number | undefined>;
2211
2218
  /**
2212
2219
  * Initial sort keys, as `field` / `+field` (ascending) or `-field` (descending).
2213
2220
  */
@@ -2512,7 +2519,7 @@ declare class ModelListFieldsSelectorComponent<T extends DataModel> implements O
2512
2519
  /** Open the field selector panel. */
2513
2520
  open(): void;
2514
2521
  /** Reorder the dropped column, placing it between its new neighbours. */
2515
- drop($event: CdkDragDrop<T, any>): void;
2522
+ drop($event: CdkDragDrop<T, T>): void;
2516
2523
  /** Show or hide column `f`, depending on its current state. */
2517
2524
  toggle(f: ModelListField): void;
2518
2525
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ModelListFieldsSelectorComponent<any>, never>;
@@ -2550,11 +2557,11 @@ declare class ModelListFieldHeaderComponent<T extends DataModel> implements OnIn
2550
2557
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<ModelListFieldHeaderComponent<any>, "data-model-list-field-header", never, { "list": { "alias": "list"; "required": true; "isSignal": true; }; "field": { "alias": "field"; "required": true; "isSignal": true; }; "sort": { "alias": "sort"; "required": false; "isSignal": true; }; "sortField": { "alias": "sortField"; "required": false; "isSignal": true; }; "menuMode": { "alias": "menuMode"; "required": false; "isSignal": true; }; "sortMode": { "alias": "sortMode"; "required": false; "isSignal": true; }; "display": { "alias": "display"; "required": false; "isSignal": true; }; }, { "display": "displayChange"; }, never, ["*"], true, never>;
2551
2558
  }
2552
2559
 
2553
- declare class ModelListSorterComponent {
2560
+ declare class ModelListSorterComponent<T extends DataModel = DataModel> {
2554
2561
  /**
2555
2562
  * ModelList reference.
2556
2563
  */
2557
- list: _angular_core.InputSignal<ModelList<any>>;
2564
+ list: _angular_core.InputSignal<ModelList<T>>;
2558
2565
  /**
2559
2566
  * Sort field name.
2560
2567
  */
@@ -2578,13 +2585,13 @@ declare class ModelListSorterComponent {
2578
2585
  cancel(): void;
2579
2586
  private getIsDown;
2580
2587
  private getIsUp;
2581
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<ModelListSorterComponent, never>;
2582
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<ModelListSorterComponent, "data-model-list-sorter", never, { "list": { "alias": "list"; "required": true; "isSignal": true; }; "field": { "alias": "field"; "required": true; "isSignal": true; }; }, {}, never, ["*"], true, never>;
2588
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<ModelListSorterComponent<any>, never>;
2589
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<ModelListSorterComponent<any>, "data-model-list-sorter", never, { "list": { "alias": "list"; "required": true; "isSignal": true; }; "field": { "alias": "field"; "required": true; "isSignal": true; }; }, {}, never, ["*"], true, never>;
2583
2590
  }
2584
2591
 
2585
- declare class ModelListFiltersSelectComponent implements OnInit {
2592
+ declare class ModelListFiltersSelectComponent<T extends DataModel = DataModel> implements OnInit {
2586
2593
  /** The list whose filters can be toggled. Required. */
2587
- list: _angular_core.InputSignal<ModelList<any>>;
2594
+ list: _angular_core.InputSignal<ModelList<T>>;
2588
2595
  /** CSS classes for the element wrapping the picker. */
2589
2596
  containerClasses: _angular_core.InputSignal<string | string[]>;
2590
2597
  /** CSS classes for an enabled filter's badge, in the badge modes. */
@@ -2611,8 +2618,8 @@ declare class ModelListFiltersSelectComponent implements OnInit {
2611
2618
  * it and resets the control, leaving the dropdown ready for the next pick.
2612
2619
  */
2613
2620
  ngOnInit(): void;
2614
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<ModelListFiltersSelectComponent, never>;
2615
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<ModelListFiltersSelectComponent, "data-model-list-filters-select", never, { "list": { "alias": "list"; "required": true; "isSignal": true; }; "containerClasses": { "alias": "containerClasses"; "required": false; "isSignal": true; }; "itemActiveClasses": { "alias": "itemActiveClasses"; "required": false; "isSignal": true; }; "itemInactiveClasses": { "alias": "itemInactiveClasses"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
2621
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<ModelListFiltersSelectComponent<any>, never>;
2622
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<ModelListFiltersSelectComponent<any>, "data-model-list-filters-select", never, { "list": { "alias": "list"; "required": true; "isSignal": true; }; "containerClasses": { "alias": "containerClasses"; "required": false; "isSignal": true; }; "itemActiveClasses": { "alias": "itemActiveClasses"; "required": false; "isSignal": true; }; "itemInactiveClasses": { "alias": "itemInactiveClasses"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
2616
2623
  }
2617
2624
 
2618
2625
  /**
@@ -2629,9 +2636,9 @@ declare class ModelListFiltersSelectComponent implements OnInit {
2629
2636
  * <data-model-list-filters [list]="list" itemClasses="col-4" />
2630
2637
  * ```
2631
2638
  */
2632
- declare class ModelListFiltersComponent {
2639
+ declare class ModelListFiltersComponent<T extends DataModel = DataModel> {
2633
2640
  /** The list whose filters are displayed. Required. */
2634
- list: _angular_core.InputSignal<ModelList<any>>;
2641
+ list: _angular_core.InputSignal<ModelList<T>>;
2635
2642
  /** CSS classes for the element wrapping all the filter widgets. */
2636
2643
  containerClasses: _angular_core.InputSignal<string | string[]>;
2637
2644
  /**
@@ -2639,8 +2646,8 @@ declare class ModelListFiltersComponent {
2639
2646
  * declaring `displayClasses` overrides this for itself.
2640
2647
  */
2641
2648
  itemClasses: _angular_core.InputSignal<string | string[]>;
2642
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<ModelListFiltersComponent, never>;
2643
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<ModelListFiltersComponent, "data-model-list-filters", never, { "list": { "alias": "list"; "required": true; "isSignal": true; }; "containerClasses": { "alias": "containerClasses"; "required": false; "isSignal": true; }; "itemClasses": { "alias": "itemClasses"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
2649
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<ModelListFiltersComponent<any>, never>;
2650
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<ModelListFiltersComponent<any>, "data-model-list-filters", never, { "list": { "alias": "list"; "required": true; "isSignal": true; }; "containerClasses": { "alias": "containerClasses"; "required": false; "isSignal": true; }; "itemClasses": { "alias": "itemClasses"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
2644
2651
  }
2645
2652
 
2646
2653
  /**
@@ -2679,22 +2686,27 @@ declare class FkselectComponent<T extends DataModel, R extends DataModel> implem
2679
2686
  search: _angular_core.InputSignal<string>;
2680
2687
  /** Label shown next to the control. */
2681
2688
  label: _angular_core.InputSignal<string>;
2689
+ /**
2690
+ * Id put on the search input, and pointed at by `[label]`'s `for`. Defaults
2691
+ * to a generated one; dispedit passes its own so its label drives this input.
2692
+ */
2693
+ inputId: _angular_core.InputSignal<string>;
2682
2694
  /** Placeholder for the search input. */
2683
2695
  placeholder: _angular_core.InputSignal<string>;
2684
2696
  /** CSS classes applied to the search input. */
2685
2697
  inputClasses: _angular_core.InputSignal<string | string[]>;
2686
2698
  /** Emits the instance the user picked from the dropdown. */
2687
- selected: _angular_core.OutputEmitterRef<T>;
2699
+ selected: _angular_core.OutputEmitterRef<R>;
2688
2700
  /** Emits when the user aborts editing (escape). */
2689
2701
  cancelled: _angular_core.OutputEmitterRef<void>;
2690
2702
  /** Emits when the user clears the selection, via {@link clearAction}. */
2691
2703
  cleared: _angular_core.OutputEmitterRef<void>;
2692
2704
  /** Form control backing the typeahead search input. */
2693
- fc: UntypedFormControl;
2705
+ fc: FormControl<R | null>;
2694
2706
  /** Search callback handed to ng-bootstrap's typeahead. */
2695
- searchFn: (text$: Observable<string>) => Observable<any>;
2707
+ searchFn: (text$: Observable<string>) => Observable<R[]>;
2696
2708
  /** Option formatter handed to ng-bootstrap's typeahead. */
2697
- displayFn: (x: any) => any;
2709
+ displayFn: (x: R | null | undefined) => string;
2698
2710
  private _tpl;
2699
2711
  private destroy$;
2700
2712
  /**
@@ -2718,7 +2730,7 @@ declare class FkselectComponent<T extends DataModel, R extends DataModel> implem
2718
2730
  */
2719
2731
  private _search;
2720
2732
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FkselectComponent<any, any>, never>;
2721
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<FkselectComponent<any, any>, "data-fkselect", never, { "model": { "alias": "model"; "required": false; "isSignal": true; }; "field": { "alias": "field"; "required": false; "isSignal": true; }; "edit": { "alias": "edit"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "queryset": { "alias": "queryset"; "required": false; "isSignal": true; }; "collection": { "alias": "collection"; "required": false; "isSignal": true; }; "update": { "alias": "update"; "required": false; "isSignal": true; }; "filter": { "alias": "filter"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "display": { "alias": "display"; "required": false; "isSignal": true; }; "search": { "alias": "search"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "inputClasses": { "alias": "inputClasses"; "required": false; "isSignal": true; }; }, { "queryset": "querysetChange"; "collection": "collectionChange"; "selected": "selected"; "cancelled": "cancelled"; "cleared": "cleared"; }, never, never, true, never>;
2733
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FkselectComponent<any, any>, "data-fkselect", never, { "model": { "alias": "model"; "required": false; "isSignal": true; }; "field": { "alias": "field"; "required": false; "isSignal": true; }; "edit": { "alias": "edit"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "queryset": { "alias": "queryset"; "required": false; "isSignal": true; }; "collection": { "alias": "collection"; "required": false; "isSignal": true; }; "update": { "alias": "update"; "required": false; "isSignal": true; }; "filter": { "alias": "filter"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "display": { "alias": "display"; "required": false; "isSignal": true; }; "search": { "alias": "search"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "inputId": { "alias": "inputId"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "inputClasses": { "alias": "inputClasses"; "required": false; "isSignal": true; }; }, { "queryset": "querysetChange"; "collection": "collectionChange"; "selected": "selected"; "cancelled": "cancelled"; "cleared": "cleared"; }, never, never, true, never>;
2722
2734
  }
2723
2735
 
2724
2736
  interface ModelListAutocompleteParams<T extends DataModel> extends ModelListFilterParams {
@@ -3263,6 +3275,8 @@ declare class ModelListSelectMultiFilter<T extends DataModel> extends ModelListS
3263
3275
  * choice. The parent constructor has already resolved the choices and
3264
3276
  * enforced the `choices`-or-`model` requirement.
3265
3277
  */
3278
+ /** The options selected from the start, as full choice objects. */
3279
+ default?: IDispEditChoice[];
3266
3280
  constructor(params: ModelListMultiSelectFilterParams<T>);
3267
3281
  }
3268
3282
 
@@ -3516,7 +3530,7 @@ declare class PChoicePipe<T extends DataModel> implements PipeTransform {
3516
3530
  * @param mode `desc` for the label alone, `code` for the raw value alone,
3517
3531
  * `both` for `[code] label`.
3518
3532
  */
3519
- transform(value: any, model: T, field: string, mode?: 'code' | 'desc' | 'both'): string;
3533
+ transform(value: unknown, model: T, field: string, mode?: 'code' | 'desc' | 'both'): string;
3520
3534
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PChoicePipe<any>, never>;
3521
3535
  static ɵpipe: _angular_core.ɵɵPipeDeclaration<PChoicePipe<any>, "pchoice", true>;
3522
3536
  }
@@ -3615,7 +3629,7 @@ declare class FactorcPipe<T extends DataModel> implements PipeTransform {
3615
3629
  * @param digitsInfo digit format, as accepted by Angular's `currency` pipe.
3616
3630
  * @param locale locale to format in; defaults to the ambient `LOCALE_ID`.
3617
3631
  */
3618
- transform(value: T | null, field: string, currencyCode?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | string, digitsInfo?: string, locale?: string): string | null;
3632
+ transform(value: T | null, field: string, currencyCode?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | (string & {}), digitsInfo?: string, locale?: string): string | null;
3619
3633
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FactorcPipe<any>, never>;
3620
3634
  static ɵpipe: _angular_core.ɵɵPipeDeclaration<FactorcPipe<any>, "factorc", true>;
3621
3635
  }
@@ -3644,7 +3658,7 @@ declare class PFactorcPipe<T extends DataModel> implements PipeTransform {
3644
3658
  * @param digitsInfo digit format, as accepted by Angular's `currency` pipe.
3645
3659
  * @param locale locale to format in; defaults to the ambient `LOCALE_ID`.
3646
3660
  */
3647
- transform(value: number | string | undefined | null, model: T | null, field: string, currencyCode?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | string, digitsInfo?: string, locale?: string): string | null;
3661
+ transform(value: number | string | undefined | null, model: T | null, field: string, currencyCode?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | (string & {}), digitsInfo?: string, locale?: string): string | null;
3648
3662
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PFactorcPipe<any>, never>;
3649
3663
  static ɵpipe: _angular_core.ɵɵPipeDeclaration<PFactorcPipe<any>, "pfactorc", true>;
3650
3664
  }
@@ -3662,6 +3676,8 @@ declare class DispeditComponent<FT, T extends DataModel, R extends DataModel> {
3662
3676
  * the model, and nothing checks it at compile time.
3663
3677
  */
3664
3678
  field: _angular_core.InputSignal<string>;
3679
+ /** Id tying the label to whichever control the editor renders. */
3680
+ readonly inputId: string;
3665
3681
  /**
3666
3682
  * External form group, for `inline` / `form` modes.
3667
3683
  *
@@ -3731,7 +3747,7 @@ declare class DispeditComponent<FT, T extends DataModel, R extends DataModel> {
3731
3747
  *
3732
3748
  * The reset control only appears in edit mode when this is not undefined.
3733
3749
  */
3734
- default: _angular_core.InputSignal<any>;
3750
+ default: _angular_core.InputSignal<unknown>;
3735
3751
  /** Input placeholder. Falls back to the field manager's `description`. */
3736
3752
  placeholder: _angular_core.InputSignal<string>;
3737
3753
  /** Help text shown under the editor. Falls back to the manager's `help`. */
@@ -3783,7 +3799,7 @@ declare class DispeditComponent<FT, T extends DataModel, R extends DataModel> {
3783
3799
  /** Display renderer actually in use, after manager lookup and overrides. */
3784
3800
  realDisplay: _angular_core.WritableSignal<DISPEDIT_DISPLAY_TYPES>;
3785
3801
  /** Raw field value backing the current display, before formatting. */
3786
- dispValue: _angular_core.WritableSignal<any>;
3802
+ dispValue: _angular_core.WritableSignal<unknown>;
3787
3803
  /** Choices actually in use, or undefined when the field has none. */
3788
3804
  realChoices: _angular_core.WritableSignal<IDispEditChoice[] | undefined>;
3789
3805
  /** Help text actually shown, after the manager fallback. */
@@ -3793,9 +3809,9 @@ declare class DispeditComponent<FT, T extends DataModel, R extends DataModel> {
3793
3809
  /** Whether the manager declares the field required. */
3794
3810
  required: _angular_core.WritableSignal<boolean>;
3795
3811
  /** Default value actually applied by {@link setDefault}. */
3796
- realDefault: _angular_core.WritableSignal<any>;
3812
+ realDefault: _angular_core.WritableSignal<unknown>;
3797
3813
  /** Latest M2M instances reported by the child select, used for display. */
3798
- m2mValues?: any[];
3814
+ m2mValues?: DataModel[];
3799
3815
  /** Form group driving the editor: the external `[form]`, or a local one. */
3800
3816
  fg: _angular_core.WritableSignal<UntypedFormGroup | undefined>;
3801
3817
  /** Resolved display function for FK / M2M options, built from `display`. */
@@ -3880,7 +3896,7 @@ declare class DispeditComponent<FT, T extends DataModel, R extends DataModel> {
3880
3896
  *
3881
3897
  * @param $event selected instance, or null when the selection was cleared.
3882
3898
  */
3883
- selectedRefItem($event: T | null): Promise<void>;
3899
+ selectedRefItem($event: R | null): Promise<void>;
3884
3900
  /**
3885
3901
  * Cache the M2M instances reported by the child select.
3886
3902
  *
@@ -3889,7 +3905,7 @@ declare class DispeditComponent<FT, T extends DataModel, R extends DataModel> {
3889
3905
  *
3890
3906
  * @param v current list of selected instances.
3891
3907
  */
3892
- onM2MValuesChanges(v: any[]): void;
3908
+ onM2MValuesChanges(v: DataModel[]): void;
3893
3909
  /** Apply cross-input consistency rules (a quill editor implies a quill viewer). */
3894
3910
  private _checks;
3895
3911
  /**
@@ -3945,6 +3961,11 @@ declare class M2mselectComponent<T extends DataModel, R extends DataModel> imple
3945
3961
  search: _angular_core.InputSignal<string>;
3946
3962
  /** Label shown next to the control. */
3947
3963
  label: _angular_core.InputSignal<string>;
3964
+ /**
3965
+ * Id put on the search input, and pointed at by `[label]`'s `for`. Defaults
3966
+ * to a generated one; dispedit passes its own so its label drives this input.
3967
+ */
3968
+ inputId: _angular_core.InputSignal<string>;
3948
3969
  /** Placeholder for the search input. */
3949
3970
  placeholder: _angular_core.InputSignal<string>;
3950
3971
  /** Text shown when nothing is selected. */
@@ -3966,17 +3987,17 @@ declare class M2mselectComponent<T extends DataModel, R extends DataModel> imple
3966
3987
  /** Form control backing the typeahead search input. */
3967
3988
  fc: UntypedFormControl;
3968
3989
  /** Search callback handed to ng-bootstrap's typeahead. */
3969
- searchFn: (text$: Observable<string>) => Observable<any>;
3990
+ searchFn: (text$: Observable<string>) => Observable<R[]>;
3970
3991
  /** Option formatter handed to ng-bootstrap's typeahead. */
3971
- displayFn: (x: any) => any;
3992
+ displayFn: (x: R | null | undefined) => string;
3972
3993
  private touched;
3973
3994
  private _tpl;
3974
3995
  private destroy$;
3975
3996
  /**
3976
3997
  * Control value accessor onChange dummy callback;
3977
- * @param fks
3998
+ * @param _fks
3978
3999
  */
3979
- onChange: (fks: number[]) => void;
4000
+ onChange: (_fks: number[]) => void;
3980
4001
  /**
3981
4002
  * Control value accessor onTouched dummy callback;
3982
4003
  */
@@ -3985,18 +4006,18 @@ declare class M2mselectComponent<T extends DataModel, R extends DataModel> imple
3985
4006
  * OnChange register from controlValueAccessor
3986
4007
  * @param onChange callback
3987
4008
  */
3988
- registerOnChange(onChange: any): void;
4009
+ registerOnChange(onChange: (fks: number[]) => void): void;
3989
4010
  /**
3990
4011
  * OnTouched register from controlValueAccessor
3991
4012
  * @param onTouched callback
3992
4013
  */
3993
- registerOnTouched(onTouched: any): void;
4014
+ registerOnTouched(onTouched: () => void): void;
3994
4015
  /**
3995
4016
  * OnInit :
3996
4017
  * - ensures queryset is available
3997
4018
  * - creates form control for input
3998
4019
  * - creates display function
3999
- * - ensure values are set from any source
4020
+ * - ensure values are set from whichever source is available
4000
4021
  */
4001
4022
  ngOnInit(): void;
4002
4023
  /**
@@ -4049,7 +4070,7 @@ declare class M2mselectComponent<T extends DataModel, R extends DataModel> imple
4049
4070
  */
4050
4071
  private _search;
4051
4072
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<M2mselectComponent<any, any>, never>;
4052
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<M2mselectComponent<any, any>, "data-m2mselect", never, { "model": { "alias": "model"; "required": false; "isSignal": true; }; "field": { "alias": "field"; "required": false; "isSignal": true; }; "edit": { "alias": "edit"; "required": false; "isSignal": true; }; "values": { "alias": "values"; "required": false; "isSignal": true; }; "queryset": { "alias": "queryset"; "required": false; "isSignal": true; }; "collection": { "alias": "collection"; "required": false; "isSignal": true; }; "filter": { "alias": "filter"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "display": { "alias": "display"; "required": false; "isSignal": true; }; "search": { "alias": "search"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "emptyLabel": { "alias": "emptyLabel"; "required": false; "isSignal": true; }; "inputClasses": { "alias": "inputClasses"; "required": false; "isSignal": true; }; }, { "values": "valuesChange"; "queryset": "querysetChange"; "collection": "collectionChange"; "added": "added"; "removed": "removed"; "cancelled": "cancelled"; "cleared": "cleared"; "detailsChanged": "detailsChanged"; "changed": "changed"; }, never, never, true, never>;
4073
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<M2mselectComponent<any, any>, "data-m2mselect", never, { "model": { "alias": "model"; "required": false; "isSignal": true; }; "field": { "alias": "field"; "required": false; "isSignal": true; }; "edit": { "alias": "edit"; "required": false; "isSignal": true; }; "values": { "alias": "values"; "required": false; "isSignal": true; }; "queryset": { "alias": "queryset"; "required": false; "isSignal": true; }; "collection": { "alias": "collection"; "required": false; "isSignal": true; }; "filter": { "alias": "filter"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "display": { "alias": "display"; "required": false; "isSignal": true; }; "search": { "alias": "search"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "inputId": { "alias": "inputId"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "emptyLabel": { "alias": "emptyLabel"; "required": false; "isSignal": true; }; "inputClasses": { "alias": "inputClasses"; "required": false; "isSignal": true; }; }, { "values": "valuesChange"; "queryset": "querysetChange"; "collection": "collectionChange"; "added": "added"; "removed": "removed"; "cancelled": "cancelled"; "cleared": "cleared"; "detailsChanged": "detailsChanged"; "changed": "changed"; }, never, never, true, never>;
4053
4074
  }
4054
4075
 
4055
4076
  /**
@@ -4096,9 +4117,9 @@ declare class FlagsComponent<T extends DataModel> implements OnInit {
4096
4117
  */
4097
4118
  action: _angular_core.InputSignal<boolean>;
4098
4119
  /** Control for the free-text "new flag" input. */
4099
- ft: FormControl;
4120
+ ft: FormControl<string | null>;
4100
4121
  /** Control for the known-flags dropdown; selecting a value adds it. */
4101
- fs: FormControl;
4122
+ fs: FormControl<string | null>;
4102
4123
  /** Flags currently on the model, rendered as badges. */
4103
4124
  curflags: _angular_core.WritableSignal<string[]>;
4104
4125
  /** Unused; kept for backwards compatibility. */
@@ -4114,9 +4135,9 @@ declare class FlagsComponent<T extends DataModel> implements OnInit {
4114
4135
  * from the model field, and wire the dropdown so that selecting a value adds
4115
4136
  * the flag (debounced 250ms).
4116
4137
  */
4117
- ngOnInit(): Promise<any>;
4138
+ ngOnInit(): void;
4118
4139
  /** Add the flag currently typed in the free-text input. */
4119
- addFreeFlag(): Promise<any>;
4140
+ addFreeFlag(): Promise<void>;
4120
4141
  /**
4121
4142
  * Add a flag and persist, unless it is already present.
4122
4143
  *
@@ -4125,12 +4146,12 @@ declare class FlagsComponent<T extends DataModel> implements OnInit {
4125
4146
  *
4126
4147
  * @param flag flag value to add.
4127
4148
  */
4128
- addFlag(flag: string): Promise<any>;
4149
+ addFlag(flag: string): Promise<void>;
4129
4150
  /**
4130
4151
  * Write the current flags back to the model field and save it, then reset
4131
4152
  * both inputs. Reports success or failure through the message service.
4132
4153
  */
4133
- update(): Promise<any>;
4154
+ update(): Promise<void>;
4134
4155
  /**
4135
4156
  * Whether the flag is already on the model.
4136
4157
  *
@@ -4143,7 +4164,7 @@ declare class FlagsComponent<T extends DataModel> implements OnInit {
4143
4164
  *
4144
4165
  * @param flag flag value to remove.
4145
4166
  */
4146
- removeFlag(flag: string): Promise<any>;
4167
+ removeFlag(flag: string): Promise<void>;
4147
4168
  /**
4148
4169
  * Switch between badge display and the editor.
4149
4170
  *
@@ -4175,9 +4196,7 @@ declare class SafeDeleteComponent<T extends DataModel> {
4175
4196
  success: boolean;
4176
4197
  }>) | undefined>;
4177
4198
  /** Translations of database objects names to human names */
4178
- checks: _angular_core.InputSignal<{
4179
- [index: string]: string;
4180
- }>;
4199
+ checks: _angular_core.InputSignal<Record<string, string>>;
4181
4200
  /** Messages to be displayed on success or error, default messages are used if not provided */
4182
4201
  messages: _angular_core.InputSignal<{
4183
4202
  success?: string;
@@ -4203,9 +4222,9 @@ declare class SafeDeleteComponent<T extends DataModel> {
4203
4222
  * First step: run the dry-run delete and open the confirmation panel with its
4204
4223
  * result. Nothing is destroyed here.
4205
4224
  *
4206
- * @param model unused; the component always acts on its own `[model]` input.
4225
+ * @param _model unused; the component always acts on its own `[model]` input.
4207
4226
  */
4208
- checkDelete(model: DataModel): Promise<void>;
4227
+ checkDelete(_model: DataModel): Promise<void>;
4209
4228
  /**
4210
4229
  * Second step: perform the actual deletion once the user has confirmed, then
4211
4230
  * close the panel and emit {@link deleted} with the dry-run result.
@@ -4456,7 +4475,7 @@ interface IDetailsFieldMetadata<T> extends IFieldMetadata<T> {
4456
4475
  * `forwardRef`, not a lambda). This is the real link of a relation: it is
4457
4476
  * what instantiates and populates the nested payload.
4458
4477
  */
4459
- model?: T;
4478
+ model?: DataModelType<DataModel>;
4460
4479
  /**
4461
4480
  * Custom deserialiser, taking precedence over `model`. Use it for payloads
4462
4481
  * that are not models — a plain shape, or a value needing transformation.
@@ -4531,7 +4550,7 @@ declare const reverseForeignKeyField: (updates: IReverseForeignKeyFieldMetadata)
4531
4550
  * @detailsField({ description: 'Labels', readonly: true })
4532
4551
  * public labels_details?: string[];
4533
4552
  */
4534
- declare const detailsField: (meta?: IDetailsFieldMetadata<any>) => FieldDecoratorFn;
4553
+ declare const detailsField: (meta?: IDetailsFieldMetadata<unknown>) => FieldDecoratorFn;
4535
4554
  /**
4536
4555
  * Declares the id half of a many-to-many relation: an array of ids, rendered
4537
4556
  * as a multi-select. Pairs with a `many: true` {@link detailsField} named
@@ -4710,7 +4729,7 @@ interface IComputedFieldMetadata<T> extends IFieldMetadata<T> {
4710
4729
  * }
4711
4730
  * }
4712
4731
  */
4713
- declare const computedField: (meta?: IComputedFieldMetadata<any>) => FieldDecoratorFn;
4732
+ declare const computedField: (meta?: IComputedFieldMetadata<unknown>) => FieldDecoratorFn;
4714
4733
 
4715
4734
  /** Severity of a {@link Message}; drives both the CSS classes and the default lifetime. */
4716
4735
  declare const enum TYPE {
@@ -4735,9 +4754,7 @@ declare enum STATUS {
4735
4754
  /**
4736
4755
  * Maps a sound key (`success`, `info`, `warning`, `error`, `notify`) to an audio file url.
4737
4756
  */
4738
- interface ISoundTypes {
4739
- [index: string]: string;
4740
- }
4757
+ type ISoundTypes = Record<string, string>;
4741
4758
  /**
4742
4759
  * Optional per-severity notification sounds ({@link ISoundTypes}) played by
4743
4760
  * {@link DataMessageService.play}.
@@ -4758,11 +4775,11 @@ declare class Message {
4758
4775
  title: string;
4759
4776
  type: TYPE;
4760
4777
  message: string;
4761
- trace: any;
4778
+ trace: unknown;
4762
4779
  ttl: number;
4763
4780
  status: string;
4764
4781
  showTrace: boolean;
4765
- constructor(title: string, type?: TYPE, message?: string, trace?: any, ttl?: number);
4782
+ constructor(title: string, type?: TYPE, message?: string, trace?: unknown, ttl?: number);
4766
4783
  /** Bootstrap alert classes matching this message's severity, bound by the message zone template. */
4767
4784
  get style(): string;
4768
4785
  /** Marks the message as dismissed so it is filtered out of the next emission. Prefer {@link DataMessageService.ack}, which also refreshes the stream. */
@@ -4806,22 +4823,22 @@ declare class DataMessageService {
4806
4823
  */
4807
4824
  close(): void;
4808
4825
  /** Reports a completed operation. Short-lived by default (3 s) and plays the `success` sound if configured. */
4809
- success(title: string, message?: string, trace?: any, timeout?: number): void;
4826
+ success(title: string, message?: string, trace?: unknown, timeout?: number): void;
4810
4827
  /** Reports neutral information the user does not have to act on. Defaults to a 4 s lifetime. */
4811
- info(title: string, message?: string, trace?: any, timeout?: number): void;
4828
+ info(title: string, message?: string, trace?: unknown, timeout?: number): void;
4812
4829
  /** Reports a problem the user should notice but that did not abort the operation. Stays 10 s by default. */
4813
- warning(title: string, message?: string, trace?: any, timeout?: number): void;
4830
+ warning(title: string, message?: string, trace?: unknown, timeout?: number): void;
4814
4831
  /**
4815
4832
  * Reports a failure, with the longest lifetime (30 s) and the `error` sound.
4816
4833
  *
4817
4834
  * Note that the `timeout` argument is currently ignored: the message is always created
4818
4835
  * with {@link DEFAULT_TIMEOUTS.DANGER}.
4819
4836
  */
4820
- danger(title: string, message?: string, trace?: any, timeout?: number): void;
4837
+ danger(title: string, message?: string, trace?: unknown, timeout?: number): void;
4821
4838
  /** Alias for {@link danger}, for callers that think in terms of "error" rather than severity. */
4822
- error(title: string, message?: string, trace?: any): void;
4839
+ error(title: string, message?: string, trace?: unknown): void;
4823
4840
  /** Logs to the console only — nothing is shown to the user and no message is queued. */
4824
- debug(title: string, message?: string, trace?: any): void;
4841
+ debug(title: string, message?: string, trace?: unknown): void;
4825
4842
  /**
4826
4843
  * Dismisses a message on the user's behalf and immediately re-emits the list, so the
4827
4844
  * message zone drops it without waiting for its ttl. Bound to the close button of
@@ -4925,13 +4942,13 @@ declare class QuerysetMock<T extends DataModel> {
4925
4942
  /** `[results, loading, meta]` combined, mirroring `Queryset.full`. */
4926
4943
  get full(): Observable<[T[], boolean, IQueryMeta]>;
4927
4944
  /** No-op returning `this`; the filter is discarded. See the class-level caveat. */
4928
- filter(params?: FilterData): QuerysetMock<T>;
4945
+ filter(_params?: FilterData): QuerysetMock<T>;
4929
4946
  /** No-op returning `this`; the ordering is discarded. See the class-level caveat. */
4930
- sort(...sorting: string[]): QuerysetMock<T>;
4947
+ sort(..._sorting: string[]): QuerysetMock<T>;
4931
4948
  /** No-op returning `this`; {@link pageSize} is left untouched. */
4932
- paginateBy(pageSize?: number): QuerysetMock<T>;
4949
+ paginateBy(_pageSize?: number): QuerysetMock<T>;
4933
4950
  /** No-op returning `this`; {@link page} is left untouched. */
4934
- setPage(page: number): QuerysetMock<T>;
4951
+ setPage(_page: number): QuerysetMock<T>;
4935
4952
  /** Always returns `{}`, since no query state is retained. */
4936
4953
  getQueryParams(): FilterData;
4937
4954
  /**
@@ -4993,13 +5010,13 @@ declare class CollectionMock<T extends DataModel> {
4993
5010
  * id, so a test that fetches two different ids gets the same instance back.
4994
5011
  * Reassign `mockValue` between calls if that matters.
4995
5012
  */
4996
- fetch(id: number, suffix?: string): Observable<T>;
5013
+ fetch(_id: number, _suffix?: string): Observable<T>;
4997
5014
  /**
4998
5015
  * Returns {@link actionValue}, ignoring the arguments. Since it does not record
4999
5016
  * the call, assert on the effects rather than on the invocation, or spy on this
5000
5017
  * method when you need to check what was requested.
5001
5018
  */
5002
- action(model: T | null, method: IHttpMethod, action: string, params: IActionParams): Observable<any>;
5019
+ action(_model: T | null, _method: IHttpMethod, _action: string, _params: IActionParams): Observable<any>;
5003
5020
  /**
5004
5021
  * Opens a {@link QuerysetMock} backed by this mock, mirroring
5005
5022
  * `Collection.queryset()`. With no `mockData` set on it, the queryset falls
@@ -5011,9 +5028,7 @@ declare class CollectionMock<T extends DataModel> {
5011
5028
  * real `Collection.list()` unwraps. Arguments are ignored, so filters are not
5012
5029
  * applied; set {@link mockValues} to whatever the filtered result should be.
5013
5030
  */
5014
- list(query?: {
5015
- [index: string]: string;
5016
- }, suffix?: string): Observable<any>;
5031
+ list(_query?: Record<string, string>, _suffix?: string): Observable<any>;
5017
5032
  /**
5018
5033
  * Builds real model instances from JSON, exactly as a live collection would —
5019
5034
  * this is the method you use to prepare {@link mockValue}/{@link mockValues}.
@@ -5027,7 +5042,7 @@ declare class CollectionMock<T extends DataModel> {
5027
5042
  * @param data JSON object or array of them
5028
5043
  * @param many set to `true` when `data` is an array
5029
5044
  */
5030
- fromJson(data: any, many?: boolean): T | any;
5045
+ fromJson(data: any, many?: boolean): any;
5031
5046
  }
5032
5047
 
5033
5048
  /**
@@ -5092,16 +5107,15 @@ interface UploadFile {
5092
5107
  /** Live status and throughput figures, updated as the transfer progresses. */
5093
5108
  progress: UploadProgress;
5094
5109
  /** Response body, parsed as JSON when possible and left as raw text otherwise. Set once done. */
5095
- response?: any;
5110
+ response?: unknown;
5096
5111
  /** HTTP status code of the response. Set once done — note that a 4xx/5xx still completes as `done`. */
5097
5112
  responseStatus?: number;
5098
- sub?: Subscription | any;
5113
+ /** Subscription of the in-flight request, used to cancel it. */
5114
+ sub?: Subscription;
5099
5115
  /** The underlying browser `File`, i.e. what is actually transferred. */
5100
5116
  nativeFile?: File;
5101
5117
  /** Response headers, parsed into a plain object. Set once done. */
5102
- responseHeaders?: {
5103
- [key: string]: string;
5104
- };
5118
+ responseHeaders?: Record<string, string>;
5105
5119
  }
5106
5120
  /**
5107
5121
  * An event emitted by the uploader on the `uploadOutput` output of the directives.
@@ -5112,7 +5126,7 @@ interface UploadFile {
5112
5126
  * `dragOver`/`dragOut`/`drop` for drop-zone hover states.
5113
5127
  */
5114
5128
  interface UploadOutput {
5115
- type: "addedToQueue" | "allAddedToQueue" | "uploading" | "done" | "start" | "cancelled" | "dragOver" | "dragOut" | "drop" | "removed" | "removedAll" | "rejected";
5129
+ type: 'addedToQueue' | 'allAddedToQueue' | 'uploading' | 'done' | 'start' | 'cancelled' | 'dragOver' | 'dragOut' | 'drop' | 'removed' | 'removedAll' | 'rejected';
5116
5130
  file?: UploadFile;
5117
5131
  nativeFile?: File;
5118
5132
  }
@@ -5124,7 +5138,7 @@ interface UploadOutput {
5124
5138
  * data); the queue-management commands only need `type` and, where relevant, `id`.
5125
5139
  */
5126
5140
  interface UploadInput {
5127
- type: "uploadAll" | "uploadFile" | "cancel" | "cancelAll" | "remove" | "removeAll";
5141
+ type: 'uploadAll' | 'uploadFile' | 'cancel' | 'cancelAll' | 'remove' | 'removeAll';
5128
5142
  /** Target endpoint for `uploadAll`/`uploadFile`. Defaults to an empty string, which posts to the current url. */
5129
5143
  url?: string;
5130
5144
  /** HTTP method to use; defaults to `POST`. */
@@ -5137,13 +5151,9 @@ interface UploadInput {
5137
5151
  /** The queued file to send with `uploadFile`; matched by identity against the queue. */
5138
5152
  file?: UploadFile;
5139
5153
  /** Extra form fields appended to the multipart body alongside the file. */
5140
- data?: {
5141
- [key: string]: string | Blob;
5142
- };
5154
+ data?: Record<string, string | Blob>;
5143
5155
  /** Extra request headers. Note the uploader uses `XMLHttpRequest` directly, so Angular HTTP interceptors — including the auth interceptor — do not apply; add any `Authorization` header here yourself. */
5144
- headers?: {
5145
- [key: string]: string;
5146
- };
5156
+ headers?: Record<string, string>;
5147
5157
  includeWebKitFormBoundary?: boolean;
5148
5158
  /** Sends credentials (cookies) cross-origin; defaults to `false`. */
5149
5159
  withCredentials?: boolean;
@@ -5273,7 +5283,7 @@ declare class NgFileDropDirective implements OnInit, OnDestroy {
5273
5283
  ngOnDestroy(): void;
5274
5284
  stopEvent: (e: Event) => void;
5275
5285
  /** Handles a drop: emits `drop`, then screens and queues the dropped files. Bound to the host `drop` event. */
5276
- onDrop(e: any): void;
5286
+ onDrop(e: DragEvent): void;
5277
5287
  /** Emits `dragOver` while a drag hovers the zone, for highlighting it. Bound to the host `dragover` event. */
5278
5288
  onDragOver(e: Event): void;
5279
5289
  /** Emits `dragOut` when a drag leaves the zone, to clear the highlight. Bound to the host `dragleave` event. */
@@ -5286,7 +5296,7 @@ declare class NgFileSelectDirective implements OnInit, OnDestroy {
5286
5296
  /** Queue limits ({@link UploaderOptions}). Read once in `ngOnInit`; later changes have no effect. */
5287
5297
  options: _angular_core.InputSignal<UploaderOptions | undefined>;
5288
5298
  /** Command channel: emit {@link UploadInput}s on it to start, cancel or clear uploads. */
5289
- uploadInput: _angular_core.InputSignal<EventEmitter<any> | undefined>;
5299
+ uploadInput: _angular_core.InputSignal<EventEmitter<UploadInput> | undefined>;
5290
5300
  /** Queue and transfer events ({@link UploadOutput}) for this input. */
5291
5301
  uploadOutput: _angular_core.OutputEmitterRef<UploadOutput>;
5292
5302
  /** The queue backing this input, created in `ngOnInit`. */
@@ -5449,7 +5459,7 @@ declare const DATA_AUTH_PARAMS: InjectionToken<AuthParams>;
5449
5459
  * Using `useClass` here would give the auth layer a private instance whose cache and
5450
5460
  * state diverge from the one the rest of the application uses.
5451
5461
  */
5452
- declare const DATA_AUTH_USER_SERVICE: InjectionToken<Collection<any>>;
5462
+ declare const DATA_AUTH_USER_SERVICE: InjectionToken<Collection<DataModel>>;
5453
5463
  /**
5454
5464
  * Generic JWT authentication service, meant to be **extended** by the application.
5455
5465
  *
@@ -5495,11 +5505,6 @@ declare const DATA_AUTH_USER_SERVICE: InjectionToken<Collection<any>>;
5495
5505
  * @typeParam UserData - the decoded JWT payload, at least an {@link IJwtBaseData}.
5496
5506
  */
5497
5507
  declare class AuthServiceBase<User extends DataModel, UserService extends Collection<User>, UserData extends IJwtBaseData> {
5498
- userService: UserService;
5499
- private _params;
5500
- private _router;
5501
- private _http;
5502
- private _msgs;
5503
5508
  /**
5504
5509
  * Emits every time {@link logout} runs, whether triggered by the user, by a failed
5505
5510
  * token refresh, or by a login error.
@@ -5514,7 +5519,13 @@ declare class AuthServiceBase<User extends DataModel, UserService extends Collec
5514
5519
  private _user$;
5515
5520
  private _token;
5516
5521
  private _fetched?;
5517
- constructor(userService: UserService, _params: AuthParams, _router: Router, _http: HttpClient, _msgs: DataMessageService);
5522
+ /** Collection used to fetch the logged-in user, from {@link DATA_AUTH_USER_SERVICE}. */
5523
+ userService: UserService;
5524
+ private _params;
5525
+ private _router;
5526
+ private _http;
5527
+ private _msgs;
5528
+ constructor();
5518
5529
  /** True when no access token is held, i.e. no session is active. Checked by {@link AuthInterceptor} before attaching a token. */
5519
5530
  get isAnonymous(): boolean;
5520
5531
  /** Current raw access token, or `null` when anonymous. Read by {@link AuthInterceptor} to build the `Authorization` header. */
@@ -5572,7 +5583,7 @@ declare class AuthServiceBase<User extends DataModel, UserService extends Collec
5572
5583
  * `'error'` when none is available), so callers can branch on the emitted string
5573
5584
  * rather than on an error callback.
5574
5585
  */
5575
- login(username: string, password: string): Observable<'success' | 'error' | 'invalid_token' | string>;
5586
+ login(username: string, password: string): Observable<'success' | 'error' | 'invalid_token' | (string & {})>;
5576
5587
  /**
5577
5588
  * Opens a session from an already-obtained token pair instead of credentials, for
5578
5589
  * flows where the tokens arrive out of band (SSO callback, impersonation, tests).
@@ -5580,7 +5591,7 @@ declare class AuthServiceBase<User extends DataModel, UserService extends Collec
5580
5591
  * Like {@link login}, it fetches the user, saves the session and reports failures as
5581
5592
  * an emitted code rather than an error.
5582
5593
  */
5583
- loginWithToken(accessToken: string, refreshToken: string): Observable<'success' | 'error' | 'invalid_token' | string>;
5594
+ loginWithToken(accessToken: string, refreshToken: string): Observable<'success' | 'error' | 'invalid_token' | (string & {})>;
5584
5595
  /**
5585
5596
  * Asks the backend to send an account reactivation token to the given user.
5586
5597
  *
@@ -5697,7 +5708,7 @@ declare class AuthInterceptor implements HttpInterceptor {
5697
5708
  * {@link DATA_AUTH_URLS}. Authenticated requests additionally get 401 handling with
5698
5709
  * automatic token refresh and replay.
5699
5710
  */
5700
- intercept(req: HttpRequest<any>, next: HttpHandler): rxjs.Observable<_angular_common_http.HttpEvent<any>>;
5711
+ intercept(req: HttpRequest<unknown>, next: HttpHandler): rxjs.Observable<_angular_common_http.HttpEvent<any>>;
5701
5712
  private _handle401Error;
5702
5713
  private _addToken;
5703
5714
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthInterceptor, never>;
@@ -5730,6 +5741,10 @@ interface BaseRouteParams {
5730
5741
  /**
5731
5742
  * Route function type.
5732
5743
  * Takes parameters, return a route.
5744
+ *
5745
+ * The parameters are `any` on purpose: a route builder is written against the
5746
+ * models it links to (`(product: Product) => [...]`), and such a function is not
5747
+ * assignable to one declared over `unknown[]`.
5733
5748
  */
5734
5749
  type RouteFn = (...args: any[]) => (string | number)[];
5735
5750
  /**
@@ -5753,18 +5768,14 @@ declare function RData<T extends BaseRouteParams>(value: T): T;
5753
5768
  * A map of route names to their {@link RouteConfigItem}, keyed by a string-literal union
5754
5769
  * so that every name is known statically and typos are compile errors.
5755
5770
  */
5756
- type RoutesConfig<T extends string> = {
5757
- readonly [Property in T]: RouteConfigItem;
5758
- };
5771
+ type RoutesConfig<T extends string> = Readonly<Record<T, RouteConfigItem>>;
5759
5772
  /** An Angular {@link Route} whose `data` is mandatory and typed as {@link BaseRouteParams}. */
5760
5773
  interface StrictRoutes extends Route {
5761
5774
  data: BaseRouteParams;
5762
5775
  }
5763
5776
  /** A route table built from {@link StrictRoutes}, i.e. where every route carries breadcrumb metadata. */
5764
5777
  type RoutesDefinition = StrictRoutes[];
5765
- interface RouteArg<T> {
5766
- [index: string]: T;
5767
- }
5778
+ type RouteArg<T> = Record<string, T>;
5768
5779
  /**
5769
5780
  * A bound, reusable reference to a route: it pairs a {@link RouteConfigItem} with a
5770
5781
  * context object from which the route's named parameters are read.
@@ -5787,7 +5798,7 @@ declare class Link<T> {
5787
5798
  * later. A missing `config` is tolerated (it warns and produces an inert link) so that
5788
5799
  * a partially-initialised template does not crash.
5789
5800
  */
5790
- constructor(config?: RouteConfigItem, context?: any, router?: Router);
5801
+ constructor(config?: RouteConfigItem, context?: object, router?: Router);
5791
5802
  private _name;
5792
5803
  /** Name of the underlying route config item, or an empty string when the link was built without config. */
5793
5804
  get name(): string;
@@ -5796,7 +5807,7 @@ declare class Link<T> {
5796
5807
  * context. Only the parameters declared by the route config are picked up; those
5797
5808
  * missing from `ctx` are reset to `null`, not left over from the previous context.
5798
5809
  */
5799
- context(ctx: any): void;
5810
+ context(ctx: object): void;
5800
5811
  /**
5801
5812
  * Builds the router commands array, resolving each declared parameter from `params`
5802
5813
  * first and falling back to the bound context.
@@ -5813,9 +5824,7 @@ declare class Link<T> {
5813
5824
  * Resolves to `false` without navigating when no router is available from either
5814
5825
  * source; otherwise it resolves with the router's own result.
5815
5826
  */
5816
- navigate(params?: {
5817
- [index: string]: T;
5818
- }, router?: Router): Promise<boolean>;
5827
+ navigate(params?: Record<string, T>, router?: Router): Promise<boolean>;
5819
5828
  }
5820
5829
 
5821
5830
  /** One rendered breadcrumb entry: its absolute path, label, icon classes, and whether it is the active leaf. */
@@ -5848,7 +5857,7 @@ declare class BreadcrumbComponent implements OnInit {
5848
5857
  /**
5849
5858
  * Additional data to be used in titleTemplate
5850
5859
  */
5851
- data: _angular_core.InputSignal<any>;
5860
+ data: _angular_core.InputSignal<object | undefined>;
5852
5861
  /**
5853
5862
  * Do we display icons ?
5854
5863
  */
@@ -5965,6 +5974,34 @@ declare class TabMemoryService {
5965
5974
  */
5966
5975
  declare const slugify: (str: string) => string;
5967
5976
 
5977
+ /**
5978
+ * Returns a document-unique DOM id, as `<prefix>-<n>`.
5979
+ *
5980
+ * Used to wire a `<label [attr.for]>` to the control its template owns: the
5981
+ * widgets in this library are instantiated repeatedly on the same page, so a
5982
+ * hard-coded id would collide and silently break the association.
5983
+ *
5984
+ * The counter is module-level and starts at zero in each JS context, so a
5985
+ * server-rendered page and its client hydration agree as long as components are
5986
+ * created in the same order — which is what Angular does.
5987
+ *
5988
+ * @param prefix identifies the widget, e.g. `data-filter`
5989
+ */
5990
+ declare function uniqueId(prefix: string): string;
5991
+
5992
+ /**
5993
+ * Teach jsdom the layout APIs CodeMirror and ProseMirror expect.
5994
+ *
5995
+ * Both editors measure the document to place the caret and decide what to
5996
+ * render, and jsdom has no layout engine: `getClientRects()` returns an empty
5997
+ * list and `elementFromPoint` does not exist at all. Zeroed rectangles are
5998
+ * enough — nothing in a spec depends on real geometry, it just has to not throw.
5999
+ *
6000
+ * Call it from `beforeEach` in any spec that instantiates an editor view. It is
6001
+ * idempotent, and only patches what is missing.
6002
+ */
6003
+ declare function installEditorDomShims(): void;
6004
+
5968
6005
  /**
5969
6006
  * Compiles (and optionally renders) an Underscore-style string template, using ERB
5970
6007
  * delimiters: `<%= expr %>` interpolates, `<% code %>` evaluates, and a `print()` helper
@@ -5980,7 +6017,15 @@ declare const slugify: (str: string) => string;
5980
6017
  * Policy without `unsafe-eval`; and the escaping delimiter `<%- expr %>` is not usable,
5981
6018
  * as the code it emits calls `_.escape` while no `_` is in scope.
5982
6019
  */
5983
- declare const tmpl: (text: string, data?: any, objectName?: string) => any;
6020
+ /**
6021
+ * A template compiled by {@link tmpl} but not yet rendered: call it with the
6022
+ * data. `source` carries the generated JS, which is handy when debugging.
6023
+ */
6024
+ type CompiledTemplate = ((data: object) => string) & {
6025
+ source?: string;
6026
+ };
6027
+ declare function tmpl(text: string, data: object, objectName?: string): string;
6028
+ declare function tmpl(text: string, data?: undefined, objectName?: string): CompiledTemplate;
5984
6029
 
5985
6030
  /**
5986
6031
  * Watches the service worker for a newly deployed version and exposes it as a simple
@@ -5993,11 +6038,17 @@ declare const tmpl: (text: string, data?: any, objectName?: string) => any;
5993
6038
  * Harmless without a service worker: the checks fail and are logged as warnings.
5994
6039
  */
5995
6040
  declare class UpdateService {
6041
+ private stable;
5996
6042
  private appRef;
5997
6043
  private swUpdate;
5998
- private stable;
5999
- constructor(appRef: ApplicationRef, swUpdate: SwUpdate);
6044
+ constructor();
6000
6045
  private _available$;
6046
+ /**
6047
+ * Asks the service worker whether a new version is available. Swallows the
6048
+ * failure raised when no service worker is registered, which is the normal
6049
+ * case in development.
6050
+ */
6051
+ private _checkForUpdate;
6001
6052
  /**
6002
6053
  * Whether a new version is ready to be activated. Starts at `false`, becomes `true` on
6003
6054
  * `VERSION_READY`, and returns to `false` once the user activates or dismisses it.
@@ -6024,5 +6075,5 @@ declare class UpdateComponent {
6024
6075
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UpdateComponent, "data-update", never, {}, {}, never, never, true, never>;
6025
6076
  }
6026
6077
 
6027
- export { AuthInterceptor, AuthServiceBase, BanAdapter, BaseFieldManager, BootstrapDataDisplayConfig, BreadcrumbComponent, ChoicePipe, Collection, CollectionMock, DATA_API_URL, DATA_AUTH_PARAMS, DATA_AUTH_SERVICE, DATA_AUTH_URLS, DATA_AUTH_USER_SERVICE, DATA_DISPLAY_CONFIG, DATA_MAX_TRANSFERSTATE_TIME, DATA_MESSAGE_SOUNDS, DEFAULT_TIMEOUTS, DataBackend, DataMessageService, DataModel, DataUploaderService, DispeditComponent, FactorPipe, FactorcPipe, FkselectComponent, FlagsComponent, Jwt, Link, M2mselectComponent, Message, MessageZoneComponent, ModelList, ModelListAutocompleteFilter, ModelListAutocompleteMultiFilter, ModelListDateFilter, ModelListDatetimeFilter, ModelListDatetimerangeFilter, ModelListFieldHeaderComponent, ModelListFieldsSelectorComponent, ModelListFilter, ModelListFilterGroup, ModelListFilters, ModelListFiltersComponent, ModelListFiltersSelectComponent, ModelListFlagsFilter, ModelListGeodistanceFilter, ModelListNumberFilter, ModelListNumberOperations, ModelListPaginatorComponent, ModelListSelectFilter, ModelListSelectMultiFilter, ModelListService, ModelListSorterComponent, ModelListTextFilter, ModelListTreeFilter, NavDriver, NgFileDropDirective, NgFileSelectDirective, NgxUploaderModule, PChoicePipe, PFactorPipe, PFactorcPipe, Queryset, RData, STATUS, SafeDeleteComponent, TYPE, TabMemoryService, UpdateComponent, UpdateService, UploadStatus, booleanField, charField, computedField, dateField, datetimeField, decimalField, detailsField, emailField, floatField, foreignKeyField, humanizeBytes, integerField, isValue, manyToManyField, passwordField, primaryField, reverseForeignKeyField, slugify, strToNumber, textField, tmpl };
6028
- export type { AuthParams, BanResults, BaseRouteParams, BlobFile, CustomField, DataDisplayConfig, DataModelFields, DataModelType, FakeDeleteResult, FieldsParams, FilterData, FilterDefaults, FiltersParams, GeoSearchResult, GetFlagsResult, IActionParams, ICollectionCacheParams, IFlagItem, IHttpMethod, IJwtBaseData, IJwtRefreshResponseJson, IJwtResponseJson, IModelListMessage, IPathTreeItem, IQueryFullResponse, IQueryMeta, IQueryMetaNav, IQueryMetaNavParams, IQueryNav, IQuerysetOptions, ISoundTypes, ModelListAutocompleteMultiParams, ModelListAutocompleteParams, ModelListFilterParams, ModelListFlagsFilterParams, ModelListGeodistanceFilterParams, ModelListMultiSelectFilterParams, ModelListNumberFilterParams, ModelListParams, ModelListSelectFilterParams, ModelListTreeParams, OutFilterData, PaginationParams, RouteConfigItem, RouteFn, RoutesConfig, RoutesDefinition, SortData, SorterDefaults, StrictRoutes, TplFn, TplFun, UploadFile, UploadInput, UploadOutput, UploadProgress, UploaderOptions };
6078
+ export { AuthInterceptor, AuthServiceBase, BanAdapter, BaseFieldManager, BootstrapDataDisplayConfig, BreadcrumbComponent, ChoicePipe, Collection, CollectionMock, DATA_API_URL, DATA_AUTH_PARAMS, DATA_AUTH_SERVICE, DATA_AUTH_URLS, DATA_AUTH_USER_SERVICE, DATA_DISPLAY_CONFIG, DATA_MAX_TRANSFERSTATE_TIME, DATA_MESSAGE_SOUNDS, DEFAULT_TIMEOUTS, DataBackend, DataMessageService, DataModel, DataUploaderService, DispeditComponent, FactorPipe, FactorcPipe, FkselectComponent, FlagsComponent, Jwt, Link, M2mselectComponent, Message, MessageZoneComponent, ModelList, ModelListAutocompleteFilter, ModelListAutocompleteMultiFilter, ModelListDateFilter, ModelListDatetimeFilter, ModelListDatetimerangeFilter, ModelListFieldHeaderComponent, ModelListFieldsSelectorComponent, ModelListFilter, ModelListFilterGroup, ModelListFilters, ModelListFiltersComponent, ModelListFiltersSelectComponent, ModelListFlagsFilter, ModelListGeodistanceFilter, ModelListNumberFilter, ModelListNumberOperations, ModelListPaginatorComponent, ModelListSelectFilter, ModelListSelectMultiFilter, ModelListService, ModelListSorterComponent, ModelListTextFilter, ModelListTreeFilter, NavDriver, NgFileDropDirective, NgFileSelectDirective, NgxUploaderModule, PChoicePipe, PFactorPipe, PFactorcPipe, Queryset, RData, STATUS, SafeDeleteComponent, TYPE, TabMemoryService, UpdateComponent, UpdateService, UploadStatus, asText, booleanField, charField, computedField, dateField, datetimeField, decimalField, detailsField, emailField, fieldValues, floatField, foreignKeyField, humanizeBytes, installEditorDomShims, integerField, isValue, manyToManyField, passwordField, primaryField, reverseForeignKeyField, slugify, strToNumber, textField, tmpl, uniqueId };
6079
+ export type { AuthParams, BanResults, BaseRouteParams, BlobFile, CompiledTemplate, CustomField, DataDisplayConfig, DataModelFields, DataModelType, FakeDeleteResult, FieldsParams, FilterData, FilterDefaults, FilterValue, FiltersParams, GeoSearchResult, GetFlagsResult, IActionParams, ICollectionCacheParams, IFlagItem, IHttpMethod, IJwtBaseData, IJwtRefreshResponseJson, IJwtResponseJson, IModelListMessage, IPathTreeItem, IQueryFullResponse, IQueryMeta, IQueryMetaNav, IQueryMetaNavParams, IQueryNav, IQuerysetOptions, ISoundTypes, ModelListAutocompleteMultiParams, ModelListAutocompleteParams, ModelListFilterParams, ModelListFlagsFilterParams, ModelListGeodistanceFilterParams, ModelListMultiSelectFilterParams, ModelListNumberFilterParams, ModelListParams, ModelListSelectFilterParams, ModelListTreeParams, OutFilterData, PaginationParams, RouteConfigItem, RouteFn, RoutesConfig, RoutesDefinition, SortData, SorterDefaults, StrictRoutes, TplFn, TplFun, UploadFile, UploadInput, UploadOutput, UploadProgress, UploaderOptions };