@solidev/data 1.0.1 → 1.1.1

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,14 @@
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';
11
+ import { SafeHtml } from '@angular/platform-browser';
12
12
 
13
13
  /**
14
14
  * Base URL of the REST API every {@link Collection} builds its endpoints on.
@@ -70,15 +70,13 @@ interface DataDisplayConfig {
70
70
  * `"inline"` renders the value in flow, `"dd"` renders it as a definition
71
71
  * list entry.
72
72
  */
73
- defaultMode: "inline" | "dd";
73
+ defaultMode: 'inline' | 'dd';
74
74
  /**
75
75
  * Class string per editor type, keyed by the editor names used in
76
76
  * `DISPEDIT_EDITOR_TYPES` (e.g. `"input.text"`, `"select"`). A missing key
77
77
  * simply yields no class.
78
78
  */
79
- inputClasses: {
80
- [index: string]: string;
81
- };
79
+ inputClasses: Record<string, string>;
82
80
  };
83
81
  }
84
82
  /**
@@ -113,406 +111,148 @@ declare const BootstrapDataDisplayConfig: DataDisplayConfig;
113
111
  declare const DATA_DISPLAY_CONFIG: InjectionToken<DataDisplayConfig>;
114
112
 
115
113
  /**
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.
114
+ * The HTTP layer every {@link Collection} sits on: it owns the API base URL,
115
+ * decides the headers, and optionally serves GET responses out of Angular's
116
+ * `TransferState` during SSR hydration.
131
117
  *
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.
118
+ * Collections never touch `HttpClient` directly, which makes this the one place
119
+ * to hook cross-cutting request concerns. The usual reason to care about this
120
+ * class is {@link headers}: subclass it, override that method to inject an auth
121
+ * token, and provide the subclass in place of the default.
135
122
  *
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.
123
+ * Requires {@link DATA_API_URL}. TransferState caching applies to GET only, and
124
+ * only when {@link DATA_MAX_TRANSFERSTATE_TIME} is provided and greater than
125
+ * zero see {@link _cachedGet} for the exact conditions.
154
126
  *
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`.
127
+ * Dependencies are resolved with `inject()`, so a subclass declares **no
128
+ * constructor**. To point one at a different API a second backend alongside
129
+ * the main one override {@link apiUrl} rather than passing a URL through
130
+ * `super()`; {@link maxTransferstateTime} is overridable the same way.
159
131
  *
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.).
132
+ * @example
133
+ * ```ts
134
+ * @Injectable({ providedIn: 'root' })
135
+ * export class AuthBackend extends DataBackend {
136
+ * protected override headers(headers: { [k: string]: string }) {
137
+ * return { ...super.headers(headers), Authorization: `Bearer ${token}` };
138
+ * }
139
+ * }
176
140
  *
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.
141
+ * @Injectable({ providedIn: 'root' })
142
+ * export class SupportBackend extends DataBackend {
143
+ * public override get apiUrl(): string {
144
+ * return 'https://support.example.com/api';
145
+ * }
146
+ * }
179
147
  *
180
- * @typeParam FT the JavaScript type the field holds on the model
148
+ * providers: [
149
+ * { provide: DATA_API_URL, useValue: 'https://api.example.com' },
150
+ * { provide: DataBackend, useClass: AuthBackend },
151
+ * ];
152
+ * ```
181
153
  */
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;
154
+ declare class DataBackend {
196
155
  /**
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}.
156
+ * Instantiation timestamp, used as the origin of the TransferState validity
157
+ * window (see {@link _cachedGet}) the window runs from backend creation, not
158
+ * from each entry's insertion.
202
159
  */
203
- required?: boolean;
160
+ private _created;
161
+ private _http;
162
+ private _transferState;
163
+ private _platform;
164
+ private _apiUrl;
165
+ private _maxTransferstateTime;
204
166
  /**
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.
167
+ * Base URL every collection URL is built on, as provided via
168
+ * {@link DATA_API_URL}. Read by `Collection.getUrl()`; exposed so custom
169
+ * collections can build URLs the same way.
208
170
  */
209
- readonly?: boolean;
171
+ get apiUrl(): string;
210
172
  /**
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.
173
+ * TransferState window in ms, from {@link DATA_MAX_TRANSFERSTATE_TIME}; `0`
174
+ * disables the cache. Overridable for the same reason as {@link apiUrl}.
214
175
  */
215
- defaultValue?: FT | ((args: any) => FT) | null;
176
+ protected get maxTransferstateTime(): number;
216
177
  /**
217
- * Display ordering weight: **higher is shown first**. `id` uses `1000`.
178
+ * Issues an arbitrary request and returns the parsed JSON body.
218
179
  *
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.
223
- */
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;
249
- /**
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.
180
+ * The general-purpose entry point, used by `Collection.action()` and
181
+ * `Collection.raw()`. GET requests are routed through {@link _cachedGet} and so
182
+ * may be answered from TransferState; every other method goes straight to the
183
+ * network and carries the body.
184
+ *
185
+ * @param collection collection the request belongs to; consulted for its
186
+ * `useTransferState` flag
187
+ * @param method HTTP method
188
+ * @param url absolute URL, normally from `Collection.getUrl()`
189
+ * @param body JSON body; ignored for GET
190
+ * @param params query parameters
191
+ * @param headers extra headers, passed through {@link headers}
192
+ *
193
+ * @return an observable of the response body
253
194
  */
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);
195
+ action<T extends DataModel, RT>(collection: Collection<T>, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', url: string, { body, params, headers }?: IActionParams): Observable<RT>;
259
196
  /**
260
- * Which editor widget `dispedit` renders for this field in edit mode.
197
+ * Issues a request whose response is taken as binary rather than JSON.
261
198
  *
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.
199
+ * Backs `Collection.blob()`; use that instead of calling this directly. Never
200
+ * cached through TransferState, whatever the method.
201
+ *
202
+ * @param collection collection the request belongs to; not used by this method
203
+ * @param method HTTP method
204
+ * @param url absolute URL
205
+ * @param body JSON body
206
+ * @param params query parameters
207
+ * @param headers extra headers, passed through {@link headers}
208
+ *
209
+ * @return an observable of the response as a `Blob`
267
210
  */
268
- editorType: DISPEDIT_EDITOR_TYPES;
211
+ blob<T extends DataModel>(collection: Collection<T>, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', url: string, { body, params, headers }?: IActionParams): Observable<Blob>;
269
212
  /**
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`, ...).
213
+ * Full-replacement write (HTTP PUT). Provided for completeness the CRUD path
214
+ * in `Collection` uses {@link post}/{@link patch} instead, so this is only
215
+ * reached by callers that need PUT semantics explicitly.
216
+ *
217
+ * @param collection collection the request belongs to; not used by this method
218
+ * @param url absolute URL
219
+ * @param body JSON body
220
+ * @param params query parameters
221
+ * @param headers extra headers, passed through {@link headers}
273
222
  */
274
- displayType: DISPEDIT_DISPLAY_TYPES;
223
+ put<T extends DataModel>(collection: Collection<T>, url: string, { body, params, headers }?: IActionParams): Observable<T>;
275
224
  /**
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.
225
+ * Creates a record (HTTP POST). Chosen by `Collection.save()`/`update()` when
226
+ * the model has no `id`.
279
227
  *
280
- * @param params metadata as passed to the decorator
228
+ * @param collection collection the request belongs to; not used by this method
229
+ * @param url absolute URL, normally the collection's list URL
230
+ * @param body JSON body
231
+ * @param params query parameters
232
+ * @param headers extra headers, passed through {@link headers}
233
+ * @returns the created record's JSON, expected to include the assigned `id`
281
234
  */
282
- constructor(params: IFieldMetadata<FT>);
235
+ post<T extends DataModel>(collection: Collection<T>, url: string, { body, params, headers }?: IActionParams): Observable<Partial<T>>;
283
236
  /**
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.
237
+ * Partially updates a record (HTTP PATCH). Chosen by
238
+ * `Collection.save()`/`update()` when the model already has an `id`.
239
+ *
240
+ * @param collection collection the request belongs to; not used by this method
241
+ * @param url absolute URL, normally the record's detail URL
242
+ * @param body JSON body, holding only the fields to change
243
+ * @param params query parameters
244
+ * @param headers extra headers, passed through {@link headers}
287
245
  */
288
- get validators(): ValidatorFn[];
246
+ patch<T extends DataModel>(collection: Collection<T>, url: string, { body, params, headers }?: IActionParams): Observable<Partial<T>>;
289
247
  /**
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.
248
+ * Deletes a record (HTTP DELETE). Backs `Collection.delete()`.
249
+ *
250
+ * @param collection collection the request belongs to; not used by this method
251
+ * @param url absolute URL of the record
252
+ * @param params query parameters
253
+ * @param headers extra headers, passed through {@link headers}
293
254
  */
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}.
299
- *
300
- * Note the seed uses `||`, so falsy current values (`0`, `''`, `false`) fall
301
- * back to the default rather than being kept.
302
- *
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`).
311
- *
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.
318
- *
319
- * @param data current model value
320
- */
321
- toJson(data: FT): any;
322
- /**
323
- * Renders the value for display. The base implementation is plain string
324
- * interpolation.
325
- *
326
- * @param data value to render
327
- * @param format reserved; ignored by the base implementation
328
- */
329
- toString(data: FT, format?: string): string;
330
- /**
331
- * Public accessor for this field's default value, invoking the
332
- * `defaultValue` factory with `args` when one was declared.
333
- *
334
- * @param args passed through to a `defaultValue` function
335
- */
336
- default(args?: any): FT | undefined;
337
- /**
338
- * Builds the base validator list: `Validators.required` when the field is
339
- * required, nothing otherwise. Subclasses call it and push their own.
340
- *
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.
347
- *
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.
352
- *
353
- * @param args passed through to a `defaultValue` function
354
- * @throws Error if `required` is set and `defaultValue` is `undefined`
355
- * @protected
356
- */
357
- protected getDefaultValue(args?: any): FT | undefined;
358
- }
359
- /**
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}.
363
- */
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>;
373
- }
374
- /**
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.
378
- */
379
- type FieldDecoratorFn = (target: any, propertyKey: string | symbol) => void;
380
-
381
- /**
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.
385
- *
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.
390
- *
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.
394
- *
395
- * @example
396
- * ```ts
397
- * @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}` };
401
- * }
402
- * }
403
- *
404
- * providers: [
405
- * { provide: DATA_API_URL, useValue: 'https://api.example.com' },
406
- * { provide: DataBackend, useClass: AuthBackend },
407
- * ];
408
- * ```
409
- */
410
- declare class DataBackend {
411
- private _http;
412
- private _transferState;
413
- private _platform;
414
- private _apiUrl;
415
- private _maxTransferstateTime;
416
- /**
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.
420
- */
421
- private _created;
422
- /**
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
429
- */
430
- constructor(_http: HttpClient, _transferState: TransferState, _platform: any, _apiUrl: string, _maxTransferstateTime?: number);
431
- /**
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.
435
- */
436
- get apiUrl(): string;
437
- /**
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.
444
- *
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}
452
- *
453
- * @return an observable of the response body
454
- */
455
- action<T extends DataModel, RT>(collection: Collection<T>, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', url: string, { body, params, headers }?: IActionParams): Observable<RT>;
456
- /**
457
- * Issues a request whose response is taken as binary rather than JSON.
458
- *
459
- * Backs `Collection.blob()`; use that instead of calling this directly. Never
460
- * cached through TransferState, whatever the method.
461
- *
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
466
- * @param params query parameters
467
- * @param headers extra headers, passed through {@link headers}
468
- *
469
- * @return an observable of the response as a `Blob`
470
- */
471
- blob<T extends DataModel>(collection: Collection<T>, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', url: string, { body, params, headers }?: IActionParams): Observable<Blob>;
472
- /**
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.
476
- *
477
- * @param collection collection the request belongs to; not used by this method
478
- * @param url absolute URL
479
- * @param body JSON body
480
- * @param params query parameters
481
- * @param headers extra headers, passed through {@link headers}
482
- */
483
- put<T extends DataModel>(collection: Collection<T>, url: string, { body, params, headers }?: IActionParams): Observable<T>;
484
- /**
485
- * Creates a record (HTTP POST). Chosen by `Collection.save()`/`update()` when
486
- * the model has no `id`.
487
- *
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`
494
- */
495
- post<T extends DataModel>(collection: Collection<T>, url: string, { body, params, headers }?: IActionParams): Observable<Partial<T>>;
496
- /**
497
- * Partially updates a record (HTTP PATCH). Chosen by
498
- * `Collection.save()`/`update()` when the model already has an `id`.
499
- *
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}
505
- */
506
- patch<T extends DataModel>(collection: Collection<T>, url: string, { body, params, headers }?: IActionParams): Observable<Partial<T>>;
507
- /**
508
- * Deletes a record (HTTP DELETE). Backs `Collection.delete()`.
509
- *
510
- * @param collection collection the request belongs to; not used by this method
511
- * @param url absolute URL of the record
512
- * @param params query parameters
513
- * @param headers extra headers, passed through {@link headers}
514
- */
515
- delete<T extends DataModel, R>(collection: Collection<T>, url: string, { params, headers }?: IActionParams): Observable<R>;
255
+ delete<T extends DataModel, R>(collection: Collection<T>, url: string, { params, headers }?: IActionParams): Observable<R>;
516
256
  /**
517
257
  * Performs a GET, transparently replaying the server-rendered response from
518
258
  * `TransferState` when that is still allowed.
@@ -559,7 +299,9 @@ declare class DataBackend {
559
299
  * @param params query parameters, typically the queryset's filters and paging
560
300
  * @param headers extra headers
561
301
  */
562
- list<T extends DataModel>(collection: Collection<T>, url: string, { params, headers }?: IActionParams): Observable<any>;
302
+ list<T extends DataModel>(collection: Collection<T>, url: string, { params, headers }?: IActionParams): Observable<{
303
+ results: Partial<T>[];
304
+ }>;
563
305
  /**
564
306
  * Resolves the headers actually sent with a request.
565
307
  *
@@ -576,470 +318,168 @@ declare class DataBackend {
576
318
  * @param headers per-request headers supplied by the caller
577
319
  * @returns the complete header set to send
578
320
  */
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; }]>;
321
+ protected headers(headers: Record<string, string>): Record<string, string>;
322
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DataBackend, never>;
585
323
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<DataBackend>;
586
324
  }
587
325
 
326
+ /** HTTP verbs this library issues. */
327
+ type IHttpMethod = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'PATCH';
588
328
  /**
589
- * Filter criteria as authored by the caller, keyed by the query parameter the API
590
- * expects (`{ status: 'active', min_price: 10 }`).
329
+ * Per-request options shared by every {@link Collection} and {@link DataBackend}
330
+ * call: body, headers and query parameters.
591
331
  *
592
- * `undefined` and `null` are meaningful here: passing either for a key *removes*
593
- * that filter, which is what lets a form bind a cleared control straight to a
594
- * queryset filter.
332
+ * All members are optional, so `{}` is a valid "nothing special" argument.
595
333
  */
596
- interface FilterData {
597
- [index: string]: string | number | boolean | undefined | null;
334
+ interface IActionParams {
335
+ /** JSON body; ignored by GET/DELETE requests. */
336
+ body?: unknown;
337
+ /** Extra headers, merged on top of whatever the backend's `headers()` adds. */
338
+ headers?: Record<string, string>;
339
+ /** Query string parameters. */
340
+ params?: Record<string, string>;
341
+ /**
342
+ * Expected response type. Note that the backend does not currently read this:
343
+ * `DataBackend.action()` always parses JSON and `DataBackend.blob()` always
344
+ * requests a blob. Use `Collection.blob()` to get binary data.
345
+ */
346
+ responseType?: 'json' | 'blob';
598
347
  }
599
348
  /**
600
- * Filter criteria flattened to strings, as they go on the wire. This is what
601
- * {@link Queryset.getQueryParams} produces.
349
+ * Shape for describing per-collection cache tuning.
350
+ *
351
+ * Currently unused by the runtime — TransferState behaviour is driven by
352
+ * `Collection.useTransferState` plus the `DATA_MAX_TRANSFERSTATE_TIME` token.
602
353
  */
603
- interface OutFilterData {
604
- [index: string]: string;
354
+ interface ICollectionCacheParams {
355
+ transferState?: {
356
+ enabled: boolean;
357
+ maxTime: number;
358
+ maxCount: number;
359
+ };
605
360
  }
606
361
  /**
607
- * A single ordering term: a field name, optionally prefixed with `-` to reverse
608
- * it (`'name'`, `'-created'`) — the DRF `ordering` convention.
362
+ * Constructor signature of a model class, letting a collection instantiate `T`
363
+ * without knowing itthis is the `model` argument passed to
364
+ * {@link Collection}'s constructor.
609
365
  */
610
- type SortData = string;
366
+ type DataModelType<T extends DataModel> = new (_coll?: Collection<T>) => T;
611
367
  /**
612
- * Construction options for a {@link Queryset}, passed through
613
- * `Collection.queryset()`.
614
- */
615
- interface IQuerysetOptions {
616
- /**
617
- * Whether the selected field list is sent to the API as a `fields` query
618
- * parameter, letting the server return only those columns. Defaults to `true`;
619
- * set it to `false` against an endpoint that does not understand `fields`.
620
- */
621
- emitFields?: boolean;
622
- /** Identifier for this queryset, used to tell several of them apart. */
623
- name?: string;
624
- /**
625
- * Intended to let the queryset sync its state with the URL. Currently inert —
626
- * nothing in the implementation reads it (the router code in `setPage` is
627
- * commented out).
628
- */
629
- route?: ActivatedRoute;
630
- /**
631
- * Intended to let the queryset push page changes into the URL. Currently inert
632
- * — see the commented-out block in `setPage`.
633
- */
634
- router?: Router;
635
- /**
636
- * Intended as the URL fragment identifying this queryset when several share a
637
- * page. Currently inert.
638
- */
639
- hash?: string;
640
- }
641
- /** Pagination request parameters: which page, and how many rows on it. */
642
- interface IQueryNav {
643
- page: number;
644
- page_size: number;
645
- }
646
- /**
647
- * Pagination state as reported by the API, and what a paginator component binds
648
- * to: total row count, current page, total pages, and the neighbouring page
649
- * numbers (`null` at either end).
650
- */
651
- interface IQueryMetaNav {
652
- /** Total number of rows matching the filters, across all pages. */
653
- count: number;
654
- /** Next page number, or `null` on the last page. */
655
- next: number | null;
656
- /** Current page number. */
657
- page: number;
658
- /** Total number of pages. */
659
- pages: number;
660
- /** Previous page number, or `null` on the first page. */
661
- prev: number | null;
662
- /** Rows per page. */
663
- size: number;
664
- }
665
- /**
666
- * Names the API uses for its pagination query parameters, echoed back so a client
667
- * can build page links without hard-coding them.
668
- */
669
- interface IQueryMetaNavParams {
670
- page: string;
671
- page_size: string;
672
- }
673
- /** Everything a list response says about itself apart from the rows. */
674
- interface IQueryMeta {
675
- nav: IQueryMetaNav;
676
- parameters: IQueryMetaNavParams;
677
- }
678
- /**
679
- * Full shape of a paginated list response: the rows under `results`, alongside
680
- * the {@link IQueryMeta} envelope. This is the contract {@link Queryset} expects
681
- * of every list endpoint it reads.
682
- */
683
- interface IQueryFullResponse<T> extends IQueryMeta {
684
- results: T[];
685
- }
686
- /**
687
- * A filtered, sorted, paginated view over a {@link Collection}, exposing its
688
- * results as observables ready to bind in a template.
368
+ * Binds a {@link DataModel} class to a REST endpoint and provides the CRUD
369
+ * operations over it.
689
370
  *
690
- * Obtained from `Collection.queryset()`. The mutators ({@link filter},
691
- * {@link sort}, {@link paginateBy}, {@link setPage}, {@link setFields}) are
692
- * chainable and only accumulate state locally **nothing is requested until
693
- * {@link get} is called**, and calling `get()` again after changing filters is
694
- * what re-runs the query.
371
+ * A collection is the seam between models and HTTP: it knows the path, builds
372
+ * URLs, hands raw JSON to the model for deserialization, and returns typed
373
+ * instances that carry a back-reference to it (so `thing.save()` works). One
374
+ * collection per endpoint, normally declared as a root-provided service.
695
375
  *
696
- * Consumers normally subscribe to {@link results} (or {@link full}) once and keep
697
- * that subscription for the component's lifetime, then call `get()` on each
698
- * change: the observables are `ReplaySubject`-backed, so every new emission flows
699
- * to the existing subscribers and late subscribers immediately get the last value.
376
+ * For anything involving filtering, sorting or pagination, go through
377
+ * {@link queryset} rather than {@link list}.
378
+ *
379
+ * The endpoint is assumed to follow Django REST Framework conventions:
380
+ * `/path` for the list, `/path/<id>` for a record, `/path/<id>/<action>` for
381
+ * custom actions, and a *paginated* list response (see {@link list}).
700
382
  *
701
383
  * @example
702
384
  * ```ts
703
- * const qs = things.queryset();
704
- * qs.results.subscribe((things) => (this.things = things));
705
- * qs.filter({ status: 'active' }).sort('-created').paginateBy(20).get().subscribe();
385
+ * @Injectable({ providedIn: 'root' })
386
+ * export class ThingService extends Collection<Thing> {
387
+ * constructor() {
388
+ * super(inject(DataBackend), '/path/to/things', Thing);
389
+ * }
390
+ * }
391
+ *
392
+ * // then
393
+ * things.fetch(1).subscribe((thing) => ...);
394
+ * things.queryset().filter({ status: 'active' }).get().subscribe((list) => ...);
706
395
  * ```
707
396
  */
708
- declare class Queryset<T extends DataModel> {
709
- protected _coll: Collection<T>;
710
- /** Filters currently staged, as last set by {@link filter}. */
711
- filterData: FilterData;
712
- /** Ordering terms currently staged, as last set by {@link sort}. */
713
- sortData: SortData[];
714
- /**
715
- * Field names to request from the API, as last set by {@link setFields}. Only
716
- * sent when `options.emitFields` is on and the list is non-empty.
717
- */
718
- fields: string[];
719
- /**
720
- * Rows per page. Note the default is 500, not the API's own default: a queryset
721
- * you never call {@link paginateBy} on asks for a large page rather than a
722
- * small one.
723
- */
724
- pageSize: number;
725
- /** Page number to request, 1-based. */
726
- page: number;
727
- /** Options this queryset was built with, merged over the defaults. */
728
- options: IQuerysetOptions;
729
- private _loading;
730
- /**
731
- * Emits `true` when a fetch starts and `false` when its results have been
732
- * processed — bind it to a spinner. Nothing is emitted before the first
733
- * {@link get}, and note that a failed request leaves this at `true`.
734
- */
735
- loading: Observable<boolean>;
736
- private _meta;
737
- /**
738
- * Emits the pagination envelope of each response — row count, page, total
739
- * pages. This is what a paginator component binds to, since {@link results}
740
- * only carries the current page's rows.
741
- */
742
- meta: Observable<IQueryMeta>;
743
- private _results;
744
- /**
745
- * Emits the current page's rows as model instances on every successful fetch.
746
- * The main output of a queryset. Being replayed, a late subscriber immediately
747
- * receives the most recent page rather than waiting for the next `get()`.
748
- */
749
- results: Observable<T[]>;
750
- private _full;
397
+ declare class Collection<T extends DataModel> {
398
+ protected backend: DataBackend;
399
+ protected path: string;
400
+ model: DataModelType<T>;
751
401
  /**
752
- * The three streams combined into `[results, loading, meta]`, for templates
753
- * that would otherwise need three `async` pipes.
402
+ * Whether GET responses for this collection may be served from / stored in
403
+ * Angular's `TransferState` during SSR hydration.
754
404
  *
755
- * Being a `combineLatest`, it emits nothing until all three have produced a
756
- * value i.e. not until the first fetch completes and then emits once per
757
- * change of any of them, so a single fetch produces several tuples as loading
758
- * flips and results land.
405
+ * On by default, but only ever takes effect when `DATA_MAX_TRANSFERSTATE_TIME`
406
+ * is provided and greater than zero. Set it to `false` on collections whose
407
+ * data is user-specific or must not be embedded in the server-rendered HTML.
759
408
  */
760
- full: Observable<[T[], boolean, IQueryMeta]>;
409
+ useTransferState: boolean;
761
410
  /**
762
- * @param _coll collection this queryset reads from
763
- * @param options queryset options, merged over the defaults (`emitFields: true`)
411
+ * @param backend HTTP layer; supplies the API base URL and applies auth headers
412
+ * @param path endpoint path appended to the backend's `apiUrl`, leading slash
413
+ * included and trailing slash omitted (e.g. `'/path/to/things'`)
414
+ * @param model model class this collection instantiates
764
415
  */
765
- constructor(_coll: Collection<T>, options?: IQuerysetOptions);
766
- private _pristine;
416
+ constructor(backend: DataBackend, path: string, model: DataModelType<T>);
767
417
  /**
768
- * `true` until the first successful fetch, `false` forever after.
418
+ * Builds a new, unsaved model instance attached to this collection.
769
419
  *
770
- * Distinguishes "no results yet" from "no results found", which a bare empty
771
- * array cannot: use it to choose between a loading placeholder and an empty
772
- * state. It is also what makes `get(false)` decide whether it may serve the
773
- * cached results.
774
- */
775
- get pristine(): boolean;
776
- /**
777
- * Stages the filters for the next {@link get}. Chainable; issues no request.
420
+ * Use it to back a creation form: the returned instance has no `id`, so the
421
+ * first `save()` POSTs it. Values passed in `data` go through the field
422
+ * managers' deserializers, exactly as if they had come from the API.
778
423
  *
779
- * @param params filter criteria. A key whose value is `null` or `undefined` is
780
- * *removed* rather than sent, so a cleared form control can be passed
781
- * straight through.
782
- * @param update when `true`, merge into the existing filters; when `false` (the
783
- * default), replace them wholesale
424
+ * @param data initial field values; omit for an empty instance
784
425
  *
785
426
  * @example
786
427
  * ```ts
787
- * qs.filter({ status: 'active' }); // replaces all filters
788
- * qs.filter({ q: search || null }, true); // merges; drops `q` when empty
428
+ * const thing = things.create({ name: 'draft' });
429
+ * thing.save().subscribe(); // POST /path/to/things
789
430
  * ```
790
431
  */
791
- filter(params?: FilterData, update?: boolean): Queryset<T>;
432
+ create(data?: Partial<T>): T;
792
433
  /**
793
- * Stages the ordering for the next {@link get}. Chainable; issues no request.
794
- *
795
- * Terms are sent joined into a single `ordering` query parameter, in the order
796
- * given, so the first term is the primary sort key.
434
+ * Turns raw API JSON into model instances attached to this collection.
797
435
  *
798
- * @param sorting field names, `-` prefixed to reverse; replaces any previous
799
- * ordering, and calling with no arguments clears it
436
+ * Every read path funnels through here, and the attachment is the point: the
437
+ * resulting instances can `save()`/`update()`/`action()` themselves. Call it
438
+ * directly when you have JSON from somewhere other than this collection's own
439
+ * requests (a websocket push, an embedded payload, a fixture).
800
440
  *
801
- * @example
802
- * ```ts
803
- * qs.sort('-created', 'name').get().subscribe();
804
- * ```
441
+ * @param data one JSON object, or an array of them
805
442
  */
806
- sort(...sorting: string[]): Queryset<T>;
443
+ fromJson(data: Partial<T>): T;
444
+ fromJson(data: Partial<T>[]): T[];
807
445
  /**
808
- * Stages the page size for the next {@link get}. Chainable; issues no request.
446
+ * Opens a filterable, sortable, paginated view over this collection.
809
447
  *
810
- * Does not reset {@link page}, so changing the size while on a high page number
811
- * can land past the end of the result set — call `setPage(1)` alongside it.
448
+ * The normal way to read lists: a {@link Queryset} accumulates query state and
449
+ * exposes results/loading/meta as observables suitable for binding straight
450
+ * into a template. Each call returns an independent queryset, so a component
451
+ * showing two filtered lists of the same endpoint creates two.
812
452
  *
813
- * @param pageSize rows per page; defaults to 20 when called with no argument,
814
- * which is *not* the queryset's own default of 500
453
+ * @param options queryset behaviour; see {@link IQuerysetOptions}
815
454
  */
816
- paginateBy(pageSize?: number): Queryset<T>;
455
+ queryset(options?: IQuerysetOptions): Queryset<T>;
817
456
  /**
818
- * Stages the page number for the next {@link get}. Chainable; issues no
819
- * request, so a paginator must call {@link get} afterwards.
457
+ * Retrieves a single record by primary key and returns it as a model.
820
458
  *
821
- * @param page 1-based page number
822
- * @param fragment currently ignored; reserved for the URL-syncing behaviour
823
- * that is commented out in the implementation
459
+ * @param id primary key
460
+ * @param prefix inserted between the collection path and the id, for endpoints
461
+ * nested under a segment
462
+ * @param suffix appended after the id, e.g. `'/detail'`
463
+ * @param params query parameters
464
+ * @param headers extra headers
465
+ *
466
+ * @example
467
+ * ```ts
468
+ * things.fetch(42).subscribe((thing) => ...); // GET /path/to/things/42
469
+ * ```
824
470
  */
825
- setPage(page?: number, fragment?: string): Queryset<T>;
471
+ fetch(id: number, { prefix, suffix, params, headers, }?: {
472
+ prefix?: string;
473
+ suffix?: string;
474
+ params?: Record<string, string>;
475
+ headers?: Record<string, string>;
476
+ }): Observable<T>;
826
477
  /**
827
- * Stages which fields the API should return. Chainable; issues no request.
478
+ * Calls a custom (non-CRUD) endpoint on this collection, either on one record
479
+ * or on the collection as a whole, and returns the raw JSON response.
828
480
  *
829
- * Lets a list view ask only for the columns it actually shows, which matters on
830
- * wide models. Sent as a `fields` query parameter, and only when
831
- * `options.emitFields` is on — with it off, this is recorded but never
832
- * transmitted.
833
- *
834
- * @param fields field names to request; an empty array means "no restriction"
835
- */
836
- setFields(fields: string[]): Queryset<T>;
837
- /**
838
- * Flattens the staged filter/sort/page/fields state into the query parameters
839
- * that will actually be sent.
840
- *
841
- * Exposed mainly for debugging and for building links that mirror the current
842
- * view; {@link get} calls it itself. Ordering goes out as `ordering`, paging as
843
- * `page`/`page_size`, and the field selection as `fields`.
844
- *
845
- * The `OutFilterData` return type is an assertion rather than a conversion —
846
- * numeric values such as `page` are still numbers at runtime, and are
847
- * stringified later by `HttpClient`.
848
- */
849
- getQueryParams(): OutFilterData;
850
- /**
851
- * Runs the staged query and pushes the outcome through {@link results},
852
- * {@link meta} and {@link loading}.
853
- *
854
- * The one method that talks to the network. Call it after each change to the
855
- * filter/sort/page state; subscribers of `results` do not need to resubscribe,
856
- * they simply receive the new page. The returned observable is `results`
857
- * itself, so subscribing to the return value is a convenience, not a
858
- * requirement — though something must subscribe for the request to be sent.
859
- *
860
- * @param refresh when `false`, an already-fetched queryset (see
861
- * {@link pristine}) short-circuits and hands back the cached `results`
862
- * without any request. Use it where a component may be re-entered and should
863
- * reuse what is already loaded. A pristine queryset always fetches, whatever
864
- * this says.
865
- * @param sync adds `db=sync` to the query, asking the API to read from its
866
- * primary rather than a replica — for the read-after-write case where
867
- * replication lag would otherwise return stale rows
868
- *
869
- * @example
870
- * ```ts
871
- * qs.get().subscribe(); // always refetches
872
- * qs.get(false).subscribe(); // reuses results if already loaded once
873
- * ```
874
- */
875
- get(refresh?: boolean, sync?: boolean): Observable<T[]>;
876
- }
877
-
878
- /** HTTP verbs this library issues. */
879
- type IHttpMethod = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'PATCH';
880
- /**
881
- * Per-request options shared by every {@link Collection} and {@link DataBackend}
882
- * call: body, headers and query parameters.
883
- *
884
- * All members are optional, so `{}` is a valid "nothing special" argument.
885
- */
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';
903
- }
904
- /**
905
- * Shape for describing per-collection cache tuning.
906
- *
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.
921
- */
922
- type DataModelType<T extends DataModel> = new (_coll?: Collection<T>) => T;
923
- /**
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.
931
- *
932
- * For anything involving filtering, sorting or pagination, go through
933
- * {@link queryset} rather than {@link list}.
934
- *
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}).
938
- *
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
- * }
947
- *
948
- * // then
949
- * things.fetch(1).subscribe((thing) => ...);
950
- * things.queryset().filter({ status: 'active' }).get().subscribe((list) => ...);
951
- * ```
952
- */
953
- declare class Collection<T extends DataModel> {
954
- protected backend: DataBackend;
955
- protected path: string;
956
- model: DataModelType<T>;
957
- /**
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.
964
- */
965
- useTransferState: boolean;
966
- /**
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
971
- */
972
- constructor(backend: DataBackend, path: string, model: DataModelType<T>);
973
- /**
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
981
- *
982
- * @example
983
- * ```ts
984
- * const thing = things.create({ name: 'draft' });
985
- * thing.save().subscribe(); // POST /path/to/things
986
- * ```
987
- */
988
- create(data?: Partial<T>): T;
989
- /**
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
998
- */
999
- fromJson(data: Partial<T>): T;
1000
- fromJson(data: Partial<T>[]): T[];
1001
- /**
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.
1008
- *
1009
- * @param options queryset behaviour; see {@link IQuerysetOptions}
1010
- */
1011
- queryset(options?: IQuerysetOptions): Queryset<T>;
1012
- /**
1013
- * Retrieves a single record by primary key and returns it as a model.
1014
- *
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
1021
- *
1022
- * @example
1023
- * ```ts
1024
- * things.fetch(42).subscribe((thing) => ...); // GET /path/to/things/42
1025
- * ```
1026
- */
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>;
1037
- /**
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.
481
+ * This is the DRF `@action` convention. `DataModel.action()` delegates here;
482
+ * call this directly for collection-level actions, which have no model.
1043
483
  *
1044
484
  * @param model record to act on, targeting `/path/to/things/<id>/<action>`, or
1045
485
  * `null` for a collection-level action targeting `/path/to/things/<action>`
@@ -1085,116 +525,668 @@ declare class Collection<T extends DataModel> {
1085
525
  * Deletes a record on the server. The local instance is left untouched — it is
1086
526
  * the caller's job to drop it from whatever list holds it.
1087
527
  *
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
528
+ * @param model record to delete; its `id` builds the URL, so an unsaved model
529
+ * would produce a request against the list URL
530
+ * @param params query parameters
531
+ * @param headers extra headers
532
+ */
533
+ delete(model: T, { params, headers }?: IActionParams): Observable<unknown>;
534
+ /**
535
+ * Fetches a list of records in one shot, discarding pagination metadata.
536
+ *
537
+ * A shortcut for the simple cases (a dropdown's options, a small fixed set).
538
+ * When you need paging, sorting, loading state or the result count, use
539
+ * {@link queryset} instead.
540
+ *
541
+ * **The response must be paginated**: this reads `result.results` and returns
542
+ * that array, so a DRF endpoint with pagination disabled — which answers with a
543
+ * bare JSON array — yields an error rather than a list. The endpoint's page size
544
+ * therefore also caps what you get back here; there is currently no way to point
545
+ * this at a different response key (see the `FIXME` in the implementation).
546
+ *
547
+ * @param query filter parameters
548
+ * @param prefix inserted before the suffix in the list URL
549
+ * @param suffix appended to the list URL, for sub-endpoints
550
+ * @param params extra query parameters, merged over `query`
551
+ * @param headers extra headers
552
+ */
553
+ list(query?: FilterData, { prefix, suffix, params, headers, }?: {
554
+ prefix?: string;
555
+ suffix?: string;
556
+ params?: Record<string, string>;
557
+ headers?: Record<string, string>;
558
+ }): Observable<T[]>;
559
+ /**
560
+ * Calls a collection-level endpoint and returns the response body **unparsed**,
561
+ * without turning it into models.
562
+ *
563
+ * The escape hatch for responses that are not a list of records: aggregates,
564
+ * stats, export summaries. It is also what {@link Queryset} uses internally,
565
+ * since a queryset needs the pagination envelope that {@link list} throws away.
566
+ *
567
+ * @param method HTTP method; defaults to GET
568
+ * @param prefix inserted in the URL as `collection_url/PREFIXSUFFIX`
569
+ * @param suffix inserted in the URL as `collection_url/PREFIXSUFFIX`
570
+ * @param params query parameters
571
+ * @param body accepted but **not currently sent** — the implementation does not
572
+ * forward it to the backend, so non-GET calls needing a body must go through
573
+ * {@link action}
574
+ * @param headers extra headers
575
+ */
576
+ raw<RT>({ method, prefix, suffix, params, body: _body, headers, }?: {
577
+ method?: IHttpMethod;
578
+ prefix?: string;
579
+ suffix?: string;
580
+ body?: unknown;
581
+ params?: Record<string, string>;
582
+ headers?: Record<string, string>;
583
+ }): Observable<RT>;
584
+ /**
585
+ * Calls a collection-level endpoint and returns the response as a `Blob`.
586
+ *
587
+ * Use it for binary or file downloads (CSV/XLSX/PDF exports) where the response
588
+ * must not be parsed as JSON.
589
+ *
590
+ * @param method HTTP method; defaults to GET
591
+ * @param prefix inserted in the URL as `collection_url/PREFIXSUFFIX`
592
+ * @param suffix inserted in the URL as `collection_url/PREFIXSUFFIX`
593
+ * @param params query parameters
594
+ * @param body accepted but **not currently sent** — the implementation does not
595
+ * forward it to the backend
596
+ * @param headers extra headers
597
+ */
598
+ blob<RT>({ method, prefix, suffix, params, body: _body, headers, }?: {
599
+ method?: IHttpMethod;
600
+ prefix?: string;
601
+ suffix?: string;
602
+ body?: unknown;
603
+ params?: Record<string, string>;
604
+ headers?: Record<string, string>;
605
+ }): Observable<Blob>;
606
+ /**
607
+ * Builds the absolute URL for this collection or one of its records.
608
+ *
609
+ * Every request the collection makes goes through here, so overriding it is the
610
+ * supported way to bend URL construction (versioned or nested endpoints).
611
+ *
612
+ * Layout is `apiUrl + path` for the list, `apiUrl + path + '/' + prefix + id +
613
+ * suffix` for a record, and `apiUrl + path + '/' + prefix + suffix` for the list
614
+ * with affixes. Note the separating slash is only emitted when there is
615
+ * something after it, so a plain list URL has no trailing slash.
616
+ *
617
+ * @param id primary key for a detail URL; `undefined` or `null` yields the list URL
618
+ * @param prefix inserted just before the id (or before the suffix)
619
+ * @param suffix appended at the very end
620
+ */
621
+ getUrl(id?: number | null, { prefix, suffix }?: {
622
+ prefix?: string;
623
+ suffix?: string;
624
+ }): string;
625
+ }
626
+
627
+ /**
628
+ * Filter criteria as authored by the caller, keyed by the query parameter the API
629
+ * expects (`{ status: 'active', min_price: 10 }`).
630
+ *
631
+ * `undefined` and `null` are meaningful here: passing either for a key *removes*
632
+ * that filter, which is what lets a form bind a cleared control straight to a
633
+ * queryset filter.
634
+ */
635
+ /**
636
+ * A value a filter can hold: whatever goes on the wire for that field. A scalar
637
+ * for most filters, an array for the multi-valued ones, `null` for "cleared".
638
+ */
639
+ type FilterValue = string | number | boolean | null | undefined | (string | number)[];
640
+ type FilterData = Record<string, string | number | boolean | undefined | null>;
641
+ /**
642
+ * Filter criteria flattened to strings, as they go on the wire. This is what
643
+ * {@link Queryset.getQueryParams} produces.
644
+ */
645
+ type OutFilterData = Record<string, string>;
646
+ /**
647
+ * A single ordering term: a field name, optionally prefixed with `-` to reverse
648
+ * it (`'name'`, `'-created'`) — the DRF `ordering` convention.
649
+ */
650
+ type SortData = string;
651
+ /**
652
+ * Construction options for a {@link Queryset}, passed through
653
+ * `Collection.queryset()`.
654
+ */
655
+ interface IQuerysetOptions {
656
+ /**
657
+ * Whether the selected field list is sent to the API as a `fields` query
658
+ * parameter, letting the server return only those columns. Defaults to `true`;
659
+ * set it to `false` against an endpoint that does not understand `fields`.
660
+ */
661
+ emitFields?: boolean;
662
+ /** Identifier for this queryset, used to tell several of them apart. */
663
+ name?: string;
664
+ /**
665
+ * Intended to let the queryset sync its state with the URL. Currently inert —
666
+ * nothing in the implementation reads it (the router code in `setPage` is
667
+ * commented out).
668
+ */
669
+ route?: ActivatedRoute;
670
+ /**
671
+ * Intended to let the queryset push page changes into the URL. Currently inert
672
+ * — see the commented-out block in `setPage`.
673
+ */
674
+ router?: Router;
675
+ /**
676
+ * Intended as the URL fragment identifying this queryset when several share a
677
+ * page. Currently inert.
678
+ */
679
+ hash?: string;
680
+ }
681
+ /** Pagination request parameters: which page, and how many rows on it. */
682
+ interface IQueryNav {
683
+ page: number;
684
+ page_size: number;
685
+ }
686
+ /**
687
+ * Pagination state as reported by the API, and what a paginator component binds
688
+ * to: total row count, current page, total pages, and the neighbouring page
689
+ * numbers (`null` at either end).
690
+ */
691
+ interface IQueryMetaNav {
692
+ /** Total number of rows matching the filters, across all pages. */
693
+ count: number;
694
+ /** Next page number, or `null` on the last page. */
695
+ next: number | null;
696
+ /** Current page number. */
697
+ page: number;
698
+ /** Total number of pages. */
699
+ pages: number;
700
+ /** Previous page number, or `null` on the first page. */
701
+ prev: number | null;
702
+ /** Rows per page. */
703
+ size: number;
704
+ }
705
+ /**
706
+ * Names the API uses for its pagination query parameters, echoed back so a client
707
+ * can build page links without hard-coding them.
708
+ */
709
+ interface IQueryMetaNavParams {
710
+ page: string;
711
+ page_size: string;
712
+ }
713
+ /** Everything a list response says about itself apart from the rows. */
714
+ interface IQueryMeta {
715
+ nav: IQueryMetaNav;
716
+ parameters: IQueryMetaNavParams;
717
+ }
718
+ /**
719
+ * Full shape of a paginated list response: the rows under `results`, alongside
720
+ * the {@link IQueryMeta} envelope. This is the contract {@link Queryset} expects
721
+ * of every list endpoint it reads.
722
+ */
723
+ interface IQueryFullResponse<T> extends IQueryMeta {
724
+ results: T[];
725
+ }
726
+ /**
727
+ * A filtered, sorted, paginated view over a {@link Collection}, exposing its
728
+ * results as observables ready to bind in a template.
729
+ *
730
+ * Obtained from `Collection.queryset()`. The mutators ({@link filter},
731
+ * {@link sort}, {@link paginateBy}, {@link setPage}, {@link setFields}) are
732
+ * chainable and only accumulate state locally — **nothing is requested until
733
+ * {@link get} is called**, and calling `get()` again after changing filters is
734
+ * what re-runs the query.
735
+ *
736
+ * Consumers normally subscribe to {@link results} (or {@link full}) once and keep
737
+ * that subscription for the component's lifetime, then call `get()` on each
738
+ * change: the observables are `ReplaySubject`-backed, so every new emission flows
739
+ * to the existing subscribers and late subscribers immediately get the last value.
740
+ *
741
+ * @example
742
+ * ```ts
743
+ * const qs = things.queryset();
744
+ * qs.results.subscribe((things) => (this.things = things));
745
+ * qs.filter({ status: 'active' }).sort('-created').paginateBy(20).get().subscribe();
746
+ * ```
747
+ */
748
+ declare class Queryset<T extends DataModel> {
749
+ protected _coll: Collection<T>;
750
+ /** Filters currently staged, as last set by {@link filter}. */
751
+ filterData: FilterData;
752
+ /** Ordering terms currently staged, as last set by {@link sort}. */
753
+ sortData: SortData[];
754
+ /**
755
+ * Field names to request from the API, as last set by {@link setFields}. Only
756
+ * sent when `options.emitFields` is on and the list is non-empty.
757
+ */
758
+ fields: string[];
759
+ /**
760
+ * Rows per page. Note the default is 500, not the API's own default: a queryset
761
+ * you never call {@link paginateBy} on asks for a large page rather than a
762
+ * small one.
763
+ */
764
+ pageSize: number;
765
+ /** Page number to request, 1-based. */
766
+ page: number;
767
+ /** Options this queryset was built with, merged over the defaults. */
768
+ options: IQuerysetOptions;
769
+ private _loading;
770
+ /**
771
+ * Emits `true` when a fetch starts and `false` when its results have been
772
+ * processed — bind it to a spinner. Nothing is emitted before the first
773
+ * {@link get}, and note that a failed request leaves this at `true`.
774
+ */
775
+ loading: Observable<boolean>;
776
+ private _meta;
777
+ /**
778
+ * Emits the pagination envelope of each response — row count, page, total
779
+ * pages. This is what a paginator component binds to, since {@link results}
780
+ * only carries the current page's rows.
781
+ */
782
+ meta: Observable<IQueryMeta>;
783
+ private _results;
784
+ /**
785
+ * Emits the current page's rows as model instances on every successful fetch.
786
+ * The main output of a queryset. Being replayed, a late subscriber immediately
787
+ * receives the most recent page rather than waiting for the next `get()`.
788
+ */
789
+ results: Observable<T[]>;
790
+ private _full;
791
+ /**
792
+ * The three streams combined into `[results, loading, meta]`, for templates
793
+ * that would otherwise need three `async` pipes.
794
+ *
795
+ * Being a `combineLatest`, it emits nothing until all three have produced a
796
+ * value — i.e. not until the first fetch completes — and then emits once per
797
+ * change of any of them, so a single fetch produces several tuples as loading
798
+ * flips and results land.
799
+ */
800
+ full: Observable<[T[], boolean, IQueryMeta]>;
801
+ /**
802
+ * @param _coll collection this queryset reads from
803
+ * @param options queryset options, merged over the defaults (`emitFields: true`)
804
+ */
805
+ constructor(_coll: Collection<T>, options?: IQuerysetOptions);
806
+ private _pristine;
807
+ /**
808
+ * `true` until the first successful fetch, `false` forever after.
809
+ *
810
+ * Distinguishes "no results yet" from "no results found", which a bare empty
811
+ * array cannot: use it to choose between a loading placeholder and an empty
812
+ * state. It is also what makes `get(false)` decide whether it may serve the
813
+ * cached results.
814
+ */
815
+ get pristine(): boolean;
816
+ /**
817
+ * Stages the filters for the next {@link get}. Chainable; issues no request.
818
+ *
819
+ * @param params filter criteria. A key whose value is `null` or `undefined` is
820
+ * *removed* rather than sent, so a cleared form control can be passed
821
+ * straight through.
822
+ * @param update when `true`, merge into the existing filters; when `false` (the
823
+ * default), replace them wholesale
824
+ *
825
+ * @example
826
+ * ```ts
827
+ * qs.filter({ status: 'active' }); // replaces all filters
828
+ * qs.filter({ q: search || null }, true); // merges; drops `q` when empty
829
+ * ```
830
+ */
831
+ filter(params?: FilterData, update?: boolean): Queryset<T>;
832
+ /**
833
+ * Stages the ordering for the next {@link get}. Chainable; issues no request.
834
+ *
835
+ * Terms are sent joined into a single `ordering` query parameter, in the order
836
+ * given, so the first term is the primary sort key.
837
+ *
838
+ * @param sorting field names, `-` prefixed to reverse; replaces any previous
839
+ * ordering, and calling with no arguments clears it
840
+ *
841
+ * @example
842
+ * ```ts
843
+ * qs.sort('-created', 'name').get().subscribe();
844
+ * ```
845
+ */
846
+ sort(...sorting: string[]): Queryset<T>;
847
+ /**
848
+ * Stages the page size for the next {@link get}. Chainable; issues no request.
849
+ *
850
+ * Does not reset {@link page}, so changing the size while on a high page number
851
+ * can land past the end of the result set — call `setPage(1)` alongside it.
852
+ *
853
+ * @param pageSize rows per page; defaults to 20 when called with no argument,
854
+ * which is *not* the queryset's own default of 500
855
+ */
856
+ paginateBy(pageSize?: number): Queryset<T>;
857
+ /**
858
+ * Stages the page number for the next {@link get}. Chainable; issues no
859
+ * request, so a paginator must call {@link get} afterwards.
860
+ *
861
+ * @param page 1-based page number
862
+ * @param _fragment currently ignored; reserved for the URL-syncing behaviour
863
+ * that is commented out in the implementation
864
+ */
865
+ setPage(page?: number, _fragment?: string): Queryset<T>;
866
+ /**
867
+ * Stages which fields the API should return. Chainable; issues no request.
868
+ *
869
+ * Lets a list view ask only for the columns it actually shows, which matters on
870
+ * wide models. Sent as a `fields` query parameter, and only when
871
+ * `options.emitFields` is on — with it off, this is recorded but never
872
+ * transmitted.
873
+ *
874
+ * @param fields field names to request; an empty array means "no restriction"
875
+ */
876
+ setFields(fields: string[]): Queryset<T>;
877
+ /**
878
+ * Flattens the staged filter/sort/page/fields state into the query parameters
879
+ * that will actually be sent.
880
+ *
881
+ * Exposed mainly for debugging and for building links that mirror the current
882
+ * view; {@link get} calls it itself. Ordering goes out as `ordering`, paging as
883
+ * `page`/`page_size`, and the field selection as `fields`.
884
+ *
885
+ * The `OutFilterData` return type is an assertion rather than a conversion —
886
+ * numeric values such as `page` are still numbers at runtime, and are
887
+ * stringified later by `HttpClient`.
888
+ */
889
+ getQueryParams(): OutFilterData;
890
+ /**
891
+ * Runs the staged query and pushes the outcome through {@link results},
892
+ * {@link meta} and {@link loading}.
893
+ *
894
+ * The one method that talks to the network. Call it after each change to the
895
+ * filter/sort/page state; subscribers of `results` do not need to resubscribe,
896
+ * they simply receive the new page. The returned observable is `results`
897
+ * itself, so subscribing to the return value is a convenience, not a
898
+ * requirement — though something must subscribe for the request to be sent.
899
+ *
900
+ * @param refresh when `false`, an already-fetched queryset (see
901
+ * {@link pristine}) short-circuits and hands back the cached `results`
902
+ * without any request. Use it where a component may be re-entered and should
903
+ * reuse what is already loaded. A pristine queryset always fetches, whatever
904
+ * this says.
905
+ * @param sync adds `db=sync` to the query, asking the API to read from its
906
+ * primary rather than a replica — for the read-after-write case where
907
+ * replication lag would otherwise return stale rows
908
+ *
909
+ * @example
910
+ * ```ts
911
+ * qs.get().subscribe(); // always refetches
912
+ * qs.get(false).subscribe(); // reuses results if already loaded once
913
+ * ```
914
+ */
915
+ get(refresh?: boolean, sync?: boolean): Observable<T[]>;
916
+ }
917
+
918
+ /**
919
+ * A single selectable option for a `select` editor or a `choice` display.
920
+ *
921
+ * `value` is what is written to the model field; `desc` is the human-readable
922
+ * label rendered in the dropdown or in display mode. Choices come either from
923
+ * the field manager (e.g. a `charField` declared with `choices`) or from the
924
+ * `[choices]` input of {@link DispeditComponent}, which overrides the manager.
925
+ */
926
+ interface IDispEditChoice {
927
+ /** Human-readable label shown to the user. */
928
+ desc: string;
929
+ /** Raw value written to the model field when the option is picked. */
930
+ value: FilterValue;
931
+ }
932
+ /**
933
+ * Every editor widget {@link DispeditComponent} knows how to render.
934
+ *
935
+ * A field manager advertises one of these through its `editorType` property,
936
+ * which is how declaring the model is enough to get the right editor. The
937
+ * `[editor]` input of {@link DispeditComponent} overrides that choice.
938
+ *
939
+ * - `input.text` / `input.email` / `input.password`: matching `<input>` type,
940
+ * with min/max length and pattern validation feedback.
941
+ * - `input.number`: numeric `<input>`, value used as-is.
942
+ * - `input.decimal`: numeric `<input>` for a `decimalField`; the value is
943
+ * divided by the manager's `factor` on load and multiplied back on save.
944
+ * - `input.date` / `input.datetime`: native date / datetime-local `<input>`.
945
+ * - `input.checkbox`: a Oui/Non button group (not an actual checkbox).
946
+ * - `textarea`: multi-line `<textarea>`.
947
+ * - `select`: `<select>` built from the resolved choices. Automatically used
948
+ * whenever choices are available, whatever the manager advertises.
949
+ * - `fkselect`: `<data-fkselect>` typeahead, set by `ForeignKeyFieldManager`.
950
+ * - `m2mselect`: `<data-m2mselect>` multi-value typeahead, set by
951
+ * `ManyToManyFieldManager`.
952
+ */
953
+ type DISPEDIT_EDITOR_TYPES = 'input.text' | 'input.email' | 'input.number' | 'input.decimal' | 'input.datetime' | 'input.date' | 'input.checkbox' | 'input.password' | 'textarea' | 'select' | 'fkselect' | 'm2mselect';
954
+ /**
955
+ * Every read-only renderer {@link DispeditComponent} knows how to produce.
956
+ *
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`.
961
+ *
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
+ * - `html`: the value injected as trusted HTML. For rich text and markdown
972
+ * fields, whose rendered form is stored alongside the source — point the
973
+ * dispedit at that `…_html` field and set `[viewer]="'html'"`. **The value is
974
+ * not sanitized**; the value goes through `SafeHtmlPipe`.
975
+ */
976
+ type DISPEDIT_DISPLAY_TYPES = 'text' | 'choice' | 'boolean' | 'decimal' | 'fkdetails' | 'm2mdetails' | 'datetime' | 'date' | 'html';
977
+
978
+ /**
979
+ * Options common to every field decorator. All are optional; each concrete
980
+ * field type extends this with its own (`ICharFieldMetadata`, etc.).
981
+ *
982
+ * Whatever you pass to a decorator is copied verbatim onto the field's manager
983
+ * instance, so these names double as the manager's public properties.
984
+ *
985
+ * @typeParam FT the JavaScript type the field holds on the model
986
+ */
987
+ interface IFieldMetadata<FT> {
988
+ /**
989
+ * Wire name of the field: the key used in the JSON payload. Defaults to the
990
+ * decorated property name, so it only needs setting when the API name and
991
+ * the TypeScript property name differ.
992
+ */
993
+ name?: string;
994
+ /** Short hint shown next to the editor. Defaults to an empty string. */
995
+ help?: string;
996
+ /**
997
+ * Human-readable label used for column titles, form labels and hover
998
+ * details. Defaults to {@link IFieldMetadata.name} when omitted.
999
+ */
1000
+ description?: string;
1001
+ /**
1002
+ * Marks the field as mandatory: adds `Validators.required` and forces a
1003
+ * value to be present on save.
1004
+ *
1005
+ * Beware: combining `required: true` with no `defaultValue` throws at class
1006
+ * definition time — see {@link BaseFieldManager.getDefaultValue}.
1007
+ */
1008
+ required?: boolean;
1009
+ /**
1010
+ * Field is display-only: it is excluded from generated form groups and never
1011
+ * pushed back to the API. Typical for server-maintained columns and for the
1012
+ * `*_details` half of a relation.
1013
+ */
1014
+ readonly?: boolean;
1015
+ /**
1016
+ * Value used when the model has no value for this field — either a constant
1017
+ * or a factory `(args) => FT` evaluated on each call, which is how you get a
1018
+ * fresh array/date per instance instead of a shared one.
1019
+ */
1020
+ defaultValue?: FT | ((args: FieldDefaultArgs) => FT) | null;
1021
+ /**
1022
+ * Display ordering weight: **higher is shown first**. `id` uses `1000`.
1023
+ *
1024
+ * `priority: -1` is the established idiom for "deserialise this field but
1025
+ * never offer it in the UI" — negative-priority fields are dropped from the
1026
+ * default column set. It is used for the id half of every relation, whose
1027
+ * `*_details` twin is what a human is meant to see.
1028
+ */
1029
+ priority?: number;
1030
+ }
1031
+ /**
1032
+ * Runtime behaviour of a single field: one manager instance is built per
1033
+ * decorated property, at class definition time, and cached in the prototype's
1034
+ * metadata map. Everything the library does with a field — serialise it,
1035
+ * validate it, build its form control, pick its widget — goes through here.
1036
+ *
1037
+ * Subclass it only to add a genuinely new field type; the shipped managers
1038
+ * (`CharFieldManager`, `ForeignKeyFieldManager`, ...) cover the usual cases.
1039
+ * Override `fromJson`/`toJson` to change the wire mapping and
1040
+ * `getValidators()` to add constraints.
1041
+ */
1042
+ declare class BaseFieldManager<FT> implements IFieldMetadata<FT> {
1043
+ /** Name is mandatory in a field manager. It is given by
1044
+ * field decorator if not directly given in field manager. */
1045
+ name: string;
1046
+ /** Human-readable label; falls back to {@link name} when not given. */
1047
+ description: string;
1048
+ /** Input hint; normalised to `''` when not given. */
1049
+ help: string;
1050
+ /** Whether a value must be present. Adds `Validators.required`. */
1051
+ required: boolean;
1052
+ /** Whether the field is excluded from forms and never written back. */
1053
+ readonly: boolean;
1054
+ /**
1055
+ * Set to `true` only by `ComputedFieldManager`. `DataModel.setFV` skips
1056
+ * assignment entirely for computed fields, making them permanently
1057
+ * read-only in the eyes of the model.
1058
+ */
1059
+ computed: boolean;
1060
+ /** Display ordering weight; higher is shown first. See {@link IFieldMetadata.priority}. */
1061
+ priority: number;
1062
+ /** Constant or factory used when the model has no value. */
1063
+ defaultValue?: FT | ((args: FieldDefaultArgs) => FT);
1064
+ /**
1065
+ * Which editor widget `dispedit` renders for this field in edit mode.
1066
+ *
1067
+ * This is the library's central idea: **declaring the model configures the
1068
+ * UI**. Managers set this themselves from what you declared — `charField`
1069
+ * flips to `'select'` as soon as `choices` is present, `foreignKeyField`
1070
+ * uses `'fkselect'`, `manyToManyField` `'m2mselect'` — so a correct model
1071
+ * yields the right editor with no per-template wiring.
1072
+ */
1073
+ editorType: DISPEDIT_EDITOR_TYPES;
1074
+ /**
1075
+ * Which read-only renderer `dispedit` uses for this field. Chosen by the
1076
+ * manager the same way as {@link editorType} (`'fkdetails'` for a foreign
1077
+ * key, `'m2mdetails'` for a m2m, `'decimal'` for `decimalField`, ...).
1078
+ */
1079
+ displayType: DISPEDIT_DISPLAY_TYPES;
1080
+ /**
1081
+ * Copies `params` onto the instance, fills in `name`/`description`/`help`
1082
+ * defaults, then eagerly resolves the default value — which means a
1083
+ * `required` field with no `defaultValue` throws here, at decoration time.
1084
+ *
1085
+ * @param params metadata as passed to the decorator
1086
+ */
1087
+ constructor(params: IFieldMetadata<FT>);
1088
+ /**
1089
+ * Angular validators for this field, rebuilt on each access. Subclasses
1090
+ * override this getter (not {@link getValidators}) to append their own —
1091
+ * `CharFieldManager` adds length/pattern, `IntegerFieldManager` min/max.
1092
+ */
1093
+ get validators(): ValidatorFn[];
1094
+ /**
1095
+ * The bare property name, stripped of any `Model__` prefix: splits
1096
+ * {@link name} on `'__'` and returns the second segment, or the whole name
1097
+ * when there is no separator.
1098
+ */
1099
+ get fname(): string;
1100
+ /**
1101
+ * Builds a typed `FormControl` for this field on a given model instance,
1102
+ * seeded with the instance's current value and falling back to the default
1103
+ * value, and wired with {@link validators}.
1104
+ *
1105
+ * Note the seed uses `||`, so falsy current values (`0`, `''`, `false`) fall
1106
+ * back to the default rather than being kept.
1107
+ *
1108
+ * @param instance model whose value seeds the control
1092
1109
  */
1093
- delete(model: T, { params, headers }?: IActionParams): Observable<any>;
1110
+ formControl(instance: DataModel): FormControl<FT | null>;
1094
1111
  /**
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).
1112
+ * Converts a raw JSON value coming from the API into the model value.
1113
+ * The base implementation passes the data straight through; typed managers
1114
+ * override it to coerce (`boolean`), parse (`date`) or instantiate
1115
+ * (`details`).
1106
1116
  *
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
1117
+ * @param data raw value read from the JSON payload
1112
1118
  */
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[]>;
1119
+ fromJson(data: unknown): FT;
1123
1120
  /**
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.
1121
+ * Converts the model value back into its JSON representation for save.
1122
+ * The base implementation passes the data straight through.
1130
1123
  *
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
1124
+ * @param data current model value
1139
1125
  */
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>;
1126
+ toJson(data: FT): unknown;
1152
1127
  /**
1153
- * Calls a collection-level endpoint and returns the response as a `Blob`.
1128
+ * Renders the value for display. The base implementation is plain string
1129
+ * interpolation.
1154
1130
  *
1155
- * Use it for binary or file downloads (CSV/XLSX/PDF exports) where the response
1156
- * must not be parsed as JSON.
1131
+ * @param data value to render
1132
+ * @param _format reserved; ignored by the base implementation
1133
+ */
1134
+ toString(data: FT, _format?: string): string;
1135
+ /**
1136
+ * Public accessor for this field's default value, invoking the
1137
+ * `defaultValue` factory with `args` when one was declared.
1157
1138
  *
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
1139
+ * @param args passed through to a `defaultValue` function
1165
1140
  */
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>;
1141
+ default(args?: FieldDefaultArgs): FT | undefined;
1178
1142
  /**
1179
- * Builds the absolute URL for this collection or one of its records.
1143
+ * Builds the base validator list: `Validators.required` when the field is
1144
+ * required, nothing otherwise. Subclasses call it and push their own.
1180
1145
  *
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).
1146
+ * @protected
1147
+ */
1148
+ protected getValidators(): ValidatorFn[];
1149
+ /**
1150
+ * Resolves the default value: calls `defaultValue` when it is a function,
1151
+ * otherwise returns it as-is.
1183
1152
  *
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.
1153
+ * **Throws** when the field is `required` and no `defaultValue` was given.
1154
+ * Because the constructor calls this, the error surfaces when the class is
1155
+ * defined (module evaluation), not when a model is instantiated a
1156
+ * `required` field without a default breaks the app at import time.
1188
1157
  *
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
1158
+ * @param args passed through to a `defaultValue` function
1159
+ * @throws Error if `required` is set and `defaultValue` is `undefined`
1160
+ * @protected
1192
1161
  */
1193
- getUrl(id?: number | null, { prefix, suffix }?: {
1194
- prefix?: string;
1195
- suffix?: string;
1196
- }): string;
1162
+ protected getDefaultValue(args?: FieldDefaultArgs): FT | undefined;
1163
+ }
1164
+ /**
1165
+ * What is actually kept in a model's metadata map: the options you passed to
1166
+ * the decorator, plus the four entries the decorator computes. This is the
1167
+ * shape returned by {@link getFieldsMetadata}.
1168
+ */
1169
+ interface IFieldStoredMetadata<FT> extends IFieldMetadata<FT> {
1170
+ /** Map key of this entry: `` `${__name}__${propertyKey}` ``. */
1171
+ self: string;
1172
+ /** Wire name; defaulted to the decorated property name. */
1173
+ name: string;
1174
+ /** Field type code, e.g. `'charField'`, `'foreignKeyField'`. */
1175
+ type: string;
1176
+ /** The live manager instance built for this field. */
1177
+ manager: BaseFieldManager<FT>;
1197
1178
  }
1179
+ /**
1180
+ * Arguments handed to a `defaultValue` factory. Free-form: callers pass
1181
+ * whatever the factory needs, and the factory knows what to expect.
1182
+ */
1183
+ type FieldDefaultArgs = Record<string, unknown>;
1184
+ /**
1185
+ * Shape of every field decorator returned by {@link genericDataField}: a plain
1186
+ * property decorator, applied for its side effect on the prototype's metadata
1187
+ * map.
1188
+ */
1189
+ type FieldDecoratorFn = (target: object, propertyKey: string | symbol) => void;
1198
1190
 
1199
1191
  /**
1200
1192
  * Resolved field metadata of a model instance, keyed by field name.
@@ -1204,7 +1196,7 @@ declare class Collection<T extends DataModel> {
1204
1196
  * filters, `dispedit`) read this map to render themselves without knowing the
1205
1197
  * concrete model.
1206
1198
  */
1207
- type DataModelFields = Readonly<Map<string, IFieldStoredMetadata<any>>>;
1199
+ type DataModelFields = Readonly<Map<string, IFieldStoredMetadata<unknown>>>;
1208
1200
  /**
1209
1201
  * Base class for every model in the library: a plain class whose fields are
1210
1202
  * declared with field decorators, which turns it into something the rest of the
@@ -1335,9 +1327,7 @@ declare class DataModel {
1335
1327
  * offending field names to their `ValidationErrors`. Note that valid fields
1336
1328
  * are still applied even when others fail — this reports, it does not roll back.
1337
1329
  */
1338
- fromJson(data: {
1339
- [index: string]: any;
1340
- }, { partial, check }?: {
1330
+ fromJson(data: object, { partial, check }?: {
1341
1331
  partial?: boolean;
1342
1332
  check?: boolean;
1343
1333
  }): null | ValidationErrors;
@@ -1378,21 +1368,13 @@ declare class DataModel {
1378
1368
  * @returns an observable of the **raw response body**, not of this model, even
1379
1369
  * when `update` is `true`
1380
1370
  */
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
- };
1371
+ action<T, RT extends object>(method: 'POST' | 'PATCH' | 'GET' | 'DELETE' | 'PUT', name: string, { body, update, updatePartial, updateCheck, params, headers, }?: {
1372
+ body?: object;
1387
1373
  update?: boolean;
1388
1374
  updatePartial?: boolean;
1389
1375
  updateCheck?: boolean;
1390
- params?: {
1391
- [_index: string]: string;
1392
- };
1393
- headers?: {
1394
- [_index: string]: string;
1395
- };
1376
+ params?: Record<string, string>;
1377
+ headers?: Record<string, string>;
1396
1378
  }): Observable<RT>;
1397
1379
  /**
1398
1380
  * Returns a field's manager — the object holding its metadata and its
@@ -1503,6 +1485,36 @@ declare class DataModel {
1503
1485
  }): Observable<this>;
1504
1486
  }
1505
1487
 
1488
+ /**
1489
+ * A model seen as the untyped property bag it also is.
1490
+ *
1491
+ * Fields are declared by decorators and reached by name at runtime — `dispedit`
1492
+ * reads `<field>_details`, filters read `filter.field` — so the data layer
1493
+ * constantly indexes models with a string the compiler cannot resolve. This is
1494
+ * the one sanctioned way to do it: it yields `unknown`, so the value has to be
1495
+ * narrowed where it is used instead of silently becoming `any` and disabling
1496
+ * type-checking for the rest of the expression.
1497
+ *
1498
+ * ```ts
1499
+ * const details = fieldValues(model)[`${field}_details`] as R[] | undefined;
1500
+ * fieldValues(model)[field] = event.id;
1501
+ * ```
1502
+ *
1503
+ * @param target model (or any object) to index by field name
1504
+ */
1505
+ declare function fieldValues(target: object): Record<string, unknown>;
1506
+ /**
1507
+ * Renders an unknown value as display text.
1508
+ *
1509
+ * `String(value)` is not enough here: values read through {@link fieldValues}
1510
+ * can be objects, and `String({})` yields `[object Object]`. Nullish becomes an
1511
+ * empty string, objects are JSON-encoded, everything else goes through
1512
+ * `String`.
1513
+ *
1514
+ * @param value value read off a model, a form or a payload
1515
+ */
1516
+ declare function asText(value: unknown): string;
1517
+
1506
1518
  /**
1507
1519
  * One column of a list: a model field (or a {@link CustomField}) together with
1508
1520
  * its display state.
@@ -1517,7 +1529,7 @@ interface ModelListField {
1517
1529
  /** The user may enable it from the field selector. */
1518
1530
  allowed: boolean;
1519
1531
  /** The field's current value/manager, `null` for custom columns. */
1520
- field: any;
1532
+ field: unknown;
1521
1533
  /** Sort key for display order, renumbered in steps of 10 on every change. */
1522
1534
  position: number;
1523
1535
  /** Ordering weight from the field metadata, higher first; `-1` means "never shown by default". */
@@ -1620,7 +1632,7 @@ declare class ModelListFields<T extends DataModel> {
1620
1632
  * @param position new sort key
1621
1633
  * @returns current enabled fields
1622
1634
  */
1623
- setPosition(fname: any, position: number): string[];
1635
+ setPosition(fname: string, position: number): string[];
1624
1636
  private _update;
1625
1637
  }
1626
1638
 
@@ -1857,19 +1869,19 @@ interface ModelListFilterParams {
1857
1869
  * Not read by the constructor. {@link ModelListFilter} initialises its value
1858
1870
  * from {@link default} instead; see the `FIXME` in the constructor body.
1859
1871
  */
1860
- value?: any;
1872
+ value?: FilterValue;
1861
1873
  /**
1862
1874
  * Free-form description used as the `title` (native tooltip) of this filter's
1863
1875
  * entry in the `data-model-list-filters-select` picker. It has no effect on
1864
1876
  * the query.
1865
1877
  */
1866
- desc?: any;
1878
+ desc?: string;
1867
1879
  /**
1868
1880
  * Initial value applied at construction time. Only some subclasses act on it
1869
1881
  * (text, select and select-multi); the others ignore it. Its expected shape
1870
1882
  * depends on the subclass.
1871
1883
  */
1872
- default?: any;
1884
+ default?: unknown;
1873
1885
  /**
1874
1886
  * CSS classes applied to the filter's container element, overriding the
1875
1887
  * `classes` input passed by the host component. Use it to give one filter a
@@ -1928,9 +1940,9 @@ declare class ModelListFilter implements ModelListFilterParams {
1928
1940
  /** Help/placeholder text. Defaults to a French string set by each subclass. */
1929
1941
  help: string;
1930
1942
  /** Tooltip text for the filter picker entry; does not affect the query. */
1931
- desc?: any;
1943
+ desc?: string;
1932
1944
  /** Initial value, applied at construction by the subclasses that support it. */
1933
- default?: any;
1945
+ default?: unknown;
1934
1946
  /** Discriminator telling {@link ModelListFiltersComponent} which widget to render. */
1935
1947
  type: FILTER_TYPE;
1936
1948
  /**
@@ -2029,7 +2041,7 @@ declare class ModelListFilter implements ModelListFilterParams {
2029
2041
  * @returns `true` when the value actually changed, `false` when it was
2030
2042
  * already set. Callers use this to avoid re-querying the list needlessly.
2031
2043
  */
2032
- set(value: any, desc: string, notify?: boolean): boolean;
2044
+ set(value: FilterValue, desc: string, notify?: boolean): boolean;
2033
2045
  /**
2034
2046
  * Removes `value` from the filter. On a multi-valued filter only that value
2035
2047
  * is dropped, which is what the chips' delete buttons call; on a
@@ -2040,7 +2052,7 @@ declare class ModelListFilter implements ModelListFilterParams {
2040
2052
  * @param notify Whether to emit on the value stream.
2041
2053
  * @returns `true` when something was actually removed.
2042
2054
  */
2043
- unset(value: any, desc: string, notify?: boolean): boolean;
2055
+ unset(value: FilterValue, desc: string, notify?: boolean): boolean;
2044
2056
  /**
2045
2057
  * Adds `value` if absent, removes it if present. Convenient for checkbox-like
2046
2058
  * widgets where the same gesture selects and deselects.
@@ -2050,7 +2062,7 @@ declare class ModelListFilter implements ModelListFilterParams {
2050
2062
  * @param notify Whether to emit on the value stream.
2051
2063
  * @returns Always `true`.
2052
2064
  */
2053
- toggle(value: any, desc: string, notify?: boolean): boolean;
2065
+ toggle(value: FilterValue, desc: string, notify?: boolean): boolean;
2054
2066
  /**
2055
2067
  * The current single value, stringified, as sent to the server. Only
2056
2068
  * meaningful for single-valued filters.
@@ -2205,9 +2217,7 @@ interface CustomField {
2205
2217
  * This is scoping/context (the current tenant, the parent object of a nested
2206
2218
  * list), not user-facing filtering — see {@link ModelListParams.filter}.
2207
2219
  */
2208
- interface FilterDefaults {
2209
- [index: string]: string | number | undefined;
2210
- }
2220
+ type FilterDefaults = Record<string, string | number | undefined>;
2211
2221
  /**
2212
2222
  * Initial sort keys, as `field` / `+field` (ascending) or `-field` (descending).
2213
2223
  */
@@ -2512,7 +2522,7 @@ declare class ModelListFieldsSelectorComponent<T extends DataModel> implements O
2512
2522
  /** Open the field selector panel. */
2513
2523
  open(): void;
2514
2524
  /** Reorder the dropped column, placing it between its new neighbours. */
2515
- drop($event: CdkDragDrop<T, any>): void;
2525
+ drop($event: CdkDragDrop<T, T>): void;
2516
2526
  /** Show or hide column `f`, depending on its current state. */
2517
2527
  toggle(f: ModelListField): void;
2518
2528
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ModelListFieldsSelectorComponent<any>, never>;
@@ -2550,11 +2560,11 @@ declare class ModelListFieldHeaderComponent<T extends DataModel> implements OnIn
2550
2560
  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
2561
  }
2552
2562
 
2553
- declare class ModelListSorterComponent {
2563
+ declare class ModelListSorterComponent<T extends DataModel = DataModel> {
2554
2564
  /**
2555
2565
  * ModelList reference.
2556
2566
  */
2557
- list: _angular_core.InputSignal<ModelList<any>>;
2567
+ list: _angular_core.InputSignal<ModelList<T>>;
2558
2568
  /**
2559
2569
  * Sort field name.
2560
2570
  */
@@ -2578,13 +2588,13 @@ declare class ModelListSorterComponent {
2578
2588
  cancel(): void;
2579
2589
  private getIsDown;
2580
2590
  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>;
2591
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<ModelListSorterComponent<any>, never>;
2592
+ 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
2593
  }
2584
2594
 
2585
- declare class ModelListFiltersSelectComponent implements OnInit {
2595
+ declare class ModelListFiltersSelectComponent<T extends DataModel = DataModel> implements OnInit {
2586
2596
  /** The list whose filters can be toggled. Required. */
2587
- list: _angular_core.InputSignal<ModelList<any>>;
2597
+ list: _angular_core.InputSignal<ModelList<T>>;
2588
2598
  /** CSS classes for the element wrapping the picker. */
2589
2599
  containerClasses: _angular_core.InputSignal<string | string[]>;
2590
2600
  /** CSS classes for an enabled filter's badge, in the badge modes. */
@@ -2611,8 +2621,8 @@ declare class ModelListFiltersSelectComponent implements OnInit {
2611
2621
  * it and resets the control, leaving the dropdown ready for the next pick.
2612
2622
  */
2613
2623
  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>;
2624
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<ModelListFiltersSelectComponent<any>, never>;
2625
+ 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
2626
  }
2617
2627
 
2618
2628
  /**
@@ -2629,9 +2639,9 @@ declare class ModelListFiltersSelectComponent implements OnInit {
2629
2639
  * <data-model-list-filters [list]="list" itemClasses="col-4" />
2630
2640
  * ```
2631
2641
  */
2632
- declare class ModelListFiltersComponent {
2642
+ declare class ModelListFiltersComponent<T extends DataModel = DataModel> {
2633
2643
  /** The list whose filters are displayed. Required. */
2634
- list: _angular_core.InputSignal<ModelList<any>>;
2644
+ list: _angular_core.InputSignal<ModelList<T>>;
2635
2645
  /** CSS classes for the element wrapping all the filter widgets. */
2636
2646
  containerClasses: _angular_core.InputSignal<string | string[]>;
2637
2647
  /**
@@ -2639,8 +2649,8 @@ declare class ModelListFiltersComponent {
2639
2649
  * declaring `displayClasses` overrides this for itself.
2640
2650
  */
2641
2651
  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>;
2652
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<ModelListFiltersComponent<any>, never>;
2653
+ 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
2654
  }
2645
2655
 
2646
2656
  /**
@@ -2679,22 +2689,27 @@ declare class FkselectComponent<T extends DataModel, R extends DataModel> implem
2679
2689
  search: _angular_core.InputSignal<string>;
2680
2690
  /** Label shown next to the control. */
2681
2691
  label: _angular_core.InputSignal<string>;
2692
+ /**
2693
+ * Id put on the search input, and pointed at by `[label]`'s `for`. Defaults
2694
+ * to a generated one; dispedit passes its own so its label drives this input.
2695
+ */
2696
+ inputId: _angular_core.InputSignal<string>;
2682
2697
  /** Placeholder for the search input. */
2683
2698
  placeholder: _angular_core.InputSignal<string>;
2684
2699
  /** CSS classes applied to the search input. */
2685
2700
  inputClasses: _angular_core.InputSignal<string | string[]>;
2686
2701
  /** Emits the instance the user picked from the dropdown. */
2687
- selected: _angular_core.OutputEmitterRef<T>;
2702
+ selected: _angular_core.OutputEmitterRef<R>;
2688
2703
  /** Emits when the user aborts editing (escape). */
2689
2704
  cancelled: _angular_core.OutputEmitterRef<void>;
2690
2705
  /** Emits when the user clears the selection, via {@link clearAction}. */
2691
2706
  cleared: _angular_core.OutputEmitterRef<void>;
2692
2707
  /** Form control backing the typeahead search input. */
2693
- fc: UntypedFormControl;
2708
+ fc: FormControl<R | null>;
2694
2709
  /** Search callback handed to ng-bootstrap's typeahead. */
2695
- searchFn: (text$: Observable<string>) => Observable<any>;
2710
+ searchFn: (text$: Observable<string>) => Observable<R[]>;
2696
2711
  /** Option formatter handed to ng-bootstrap's typeahead. */
2697
- displayFn: (x: any) => any;
2712
+ displayFn: (x: R | null | undefined) => string;
2698
2713
  private _tpl;
2699
2714
  private destroy$;
2700
2715
  /**
@@ -2718,7 +2733,7 @@ declare class FkselectComponent<T extends DataModel, R extends DataModel> implem
2718
2733
  */
2719
2734
  private _search;
2720
2735
  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>;
2736
+ 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
2737
  }
2723
2738
 
2724
2739
  interface ModelListAutocompleteParams<T extends DataModel> extends ModelListFilterParams {
@@ -3263,6 +3278,8 @@ declare class ModelListSelectMultiFilter<T extends DataModel> extends ModelListS
3263
3278
  * choice. The parent constructor has already resolved the choices and
3264
3279
  * enforced the `choices`-or-`model` requirement.
3265
3280
  */
3281
+ /** The options selected from the start, as full choice objects. */
3282
+ default?: IDispEditChoice[];
3266
3283
  constructor(params: ModelListMultiSelectFilterParams<T>);
3267
3284
  }
3268
3285
 
@@ -3516,7 +3533,7 @@ declare class PChoicePipe<T extends DataModel> implements PipeTransform {
3516
3533
  * @param mode `desc` for the label alone, `code` for the raw value alone,
3517
3534
  * `both` for `[code] label`.
3518
3535
  */
3519
- transform(value: any, model: T, field: string, mode?: 'code' | 'desc' | 'both'): string;
3536
+ transform(value: unknown, model: T, field: string, mode?: 'code' | 'desc' | 'both'): string;
3520
3537
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PChoicePipe<any>, never>;
3521
3538
  static ɵpipe: _angular_core.ɵɵPipeDeclaration<PChoicePipe<any>, "pchoice", true>;
3522
3539
  }
@@ -3615,7 +3632,7 @@ declare class FactorcPipe<T extends DataModel> implements PipeTransform {
3615
3632
  * @param digitsInfo digit format, as accepted by Angular's `currency` pipe.
3616
3633
  * @param locale locale to format in; defaults to the ambient `LOCALE_ID`.
3617
3634
  */
3618
- transform(value: T | null, field: string, currencyCode?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | string, digitsInfo?: string, locale?: string): string | null;
3635
+ transform(value: T | null, field: string, currencyCode?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | (string & {}), digitsInfo?: string, locale?: string): string | null;
3619
3636
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FactorcPipe<any>, never>;
3620
3637
  static ɵpipe: _angular_core.ɵɵPipeDeclaration<FactorcPipe<any>, "factorc", true>;
3621
3638
  }
@@ -3644,7 +3661,7 @@ declare class PFactorcPipe<T extends DataModel> implements PipeTransform {
3644
3661
  * @param digitsInfo digit format, as accepted by Angular's `currency` pipe.
3645
3662
  * @param locale locale to format in; defaults to the ambient `LOCALE_ID`.
3646
3663
  */
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;
3664
+ 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
3665
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PFactorcPipe<any>, never>;
3649
3666
  static ɵpipe: _angular_core.ɵɵPipeDeclaration<PFactorcPipe<any>, "pfactorc", true>;
3650
3667
  }
@@ -3662,6 +3679,8 @@ declare class DispeditComponent<FT, T extends DataModel, R extends DataModel> {
3662
3679
  * the model, and nothing checks it at compile time.
3663
3680
  */
3664
3681
  field: _angular_core.InputSignal<string>;
3682
+ /** Id tying the label to whichever control the editor renders. */
3683
+ readonly inputId: string;
3665
3684
  /**
3666
3685
  * External form group, for `inline` / `form` modes.
3667
3686
  *
@@ -3722,8 +3741,8 @@ declare class DispeditComponent<FT, T extends DataModel, R extends DataModel> {
3722
3741
  /**
3723
3742
  * Forces a display renderer, bypassing the field manager's `displayType`.
3724
3743
  *
3725
- * Writable because setting `[editor]="'quill'"` without a viewer makes the
3726
- * component select the `quill` viewer for you.
3744
+ * Writable so that cross-input consistency rules can settle on a renderer;
3745
+ * see {@link _checks}.
3727
3746
  */
3728
3747
  viewer: _angular_core.ModelSignal<DISPEDIT_DISPLAY_TYPES | undefined>;
3729
3748
  /**
@@ -3731,7 +3750,7 @@ declare class DispeditComponent<FT, T extends DataModel, R extends DataModel> {
3731
3750
  *
3732
3751
  * The reset control only appears in edit mode when this is not undefined.
3733
3752
  */
3734
- default: _angular_core.InputSignal<any>;
3753
+ default: _angular_core.InputSignal<unknown>;
3735
3754
  /** Input placeholder. Falls back to the field manager's `description`. */
3736
3755
  placeholder: _angular_core.InputSignal<string>;
3737
3756
  /** Help text shown under the editor. Falls back to the manager's `help`. */
@@ -3783,7 +3802,7 @@ declare class DispeditComponent<FT, T extends DataModel, R extends DataModel> {
3783
3802
  /** Display renderer actually in use, after manager lookup and overrides. */
3784
3803
  realDisplay: _angular_core.WritableSignal<DISPEDIT_DISPLAY_TYPES>;
3785
3804
  /** Raw field value backing the current display, before formatting. */
3786
- dispValue: _angular_core.WritableSignal<any>;
3805
+ dispValue: _angular_core.WritableSignal<unknown>;
3787
3806
  /** Choices actually in use, or undefined when the field has none. */
3788
3807
  realChoices: _angular_core.WritableSignal<IDispEditChoice[] | undefined>;
3789
3808
  /** Help text actually shown, after the manager fallback. */
@@ -3793,9 +3812,9 @@ declare class DispeditComponent<FT, T extends DataModel, R extends DataModel> {
3793
3812
  /** Whether the manager declares the field required. */
3794
3813
  required: _angular_core.WritableSignal<boolean>;
3795
3814
  /** Default value actually applied by {@link setDefault}. */
3796
- realDefault: _angular_core.WritableSignal<any>;
3815
+ realDefault: _angular_core.WritableSignal<unknown>;
3797
3816
  /** Latest M2M instances reported by the child select, used for display. */
3798
- m2mValues?: any[];
3817
+ m2mValues?: DataModel[];
3799
3818
  /** Form group driving the editor: the external `[form]`, or a local one. */
3800
3819
  fg: _angular_core.WritableSignal<UntypedFormGroup | undefined>;
3801
3820
  /** Resolved display function for FK / M2M options, built from `display`. */
@@ -3880,7 +3899,7 @@ declare class DispeditComponent<FT, T extends DataModel, R extends DataModel> {
3880
3899
  *
3881
3900
  * @param $event selected instance, or null when the selection was cleared.
3882
3901
  */
3883
- selectedRefItem($event: T | null): Promise<void>;
3902
+ selectedRefItem($event: R | null): Promise<void>;
3884
3903
  /**
3885
3904
  * Cache the M2M instances reported by the child select.
3886
3905
  *
@@ -3889,8 +3908,14 @@ declare class DispeditComponent<FT, T extends DataModel, R extends DataModel> {
3889
3908
  *
3890
3909
  * @param v current list of selected instances.
3891
3910
  */
3892
- onM2MValuesChanges(v: any[]): void;
3893
- /** Apply cross-input consistency rules (a quill editor implies a quill viewer). */
3911
+ onM2MValuesChanges(v: DataModel[]): void;
3912
+ /**
3913
+ * Apply cross-input consistency rules.
3914
+ *
3915
+ * Nothing to reconcile at the moment: the one rule that lived here paired the
3916
+ * `quill` editor with the `quill` viewer, and neither exists any more. Kept
3917
+ * as the place such rules belong, since `_init` already calls it.
3918
+ */
3894
3919
  private _checks;
3895
3920
  /**
3896
3921
  * Save value to database, only if model have an id.
@@ -3945,6 +3970,11 @@ declare class M2mselectComponent<T extends DataModel, R extends DataModel> imple
3945
3970
  search: _angular_core.InputSignal<string>;
3946
3971
  /** Label shown next to the control. */
3947
3972
  label: _angular_core.InputSignal<string>;
3973
+ /**
3974
+ * Id put on the search input, and pointed at by `[label]`'s `for`. Defaults
3975
+ * to a generated one; dispedit passes its own so its label drives this input.
3976
+ */
3977
+ inputId: _angular_core.InputSignal<string>;
3948
3978
  /** Placeholder for the search input. */
3949
3979
  placeholder: _angular_core.InputSignal<string>;
3950
3980
  /** Text shown when nothing is selected. */
@@ -3966,17 +3996,17 @@ declare class M2mselectComponent<T extends DataModel, R extends DataModel> imple
3966
3996
  /** Form control backing the typeahead search input. */
3967
3997
  fc: UntypedFormControl;
3968
3998
  /** Search callback handed to ng-bootstrap's typeahead. */
3969
- searchFn: (text$: Observable<string>) => Observable<any>;
3999
+ searchFn: (text$: Observable<string>) => Observable<R[]>;
3970
4000
  /** Option formatter handed to ng-bootstrap's typeahead. */
3971
- displayFn: (x: any) => any;
4001
+ displayFn: (x: R | null | undefined) => string;
3972
4002
  private touched;
3973
4003
  private _tpl;
3974
4004
  private destroy$;
3975
4005
  /**
3976
4006
  * Control value accessor onChange dummy callback;
3977
- * @param fks
4007
+ * @param _fks
3978
4008
  */
3979
- onChange: (fks: number[]) => void;
4009
+ onChange: (_fks: number[]) => void;
3980
4010
  /**
3981
4011
  * Control value accessor onTouched dummy callback;
3982
4012
  */
@@ -3985,18 +4015,18 @@ declare class M2mselectComponent<T extends DataModel, R extends DataModel> imple
3985
4015
  * OnChange register from controlValueAccessor
3986
4016
  * @param onChange callback
3987
4017
  */
3988
- registerOnChange(onChange: any): void;
4018
+ registerOnChange(onChange: (fks: number[]) => void): void;
3989
4019
  /**
3990
4020
  * OnTouched register from controlValueAccessor
3991
4021
  * @param onTouched callback
3992
4022
  */
3993
- registerOnTouched(onTouched: any): void;
4023
+ registerOnTouched(onTouched: () => void): void;
3994
4024
  /**
3995
4025
  * OnInit :
3996
4026
  * - ensures queryset is available
3997
4027
  * - creates form control for input
3998
4028
  * - creates display function
3999
- * - ensure values are set from any source
4029
+ * - ensure values are set from whichever source is available
4000
4030
  */
4001
4031
  ngOnInit(): void;
4002
4032
  /**
@@ -4049,7 +4079,7 @@ declare class M2mselectComponent<T extends DataModel, R extends DataModel> imple
4049
4079
  */
4050
4080
  private _search;
4051
4081
  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>;
4082
+ 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
4083
  }
4054
4084
 
4055
4085
  /**
@@ -4096,9 +4126,9 @@ declare class FlagsComponent<T extends DataModel> implements OnInit {
4096
4126
  */
4097
4127
  action: _angular_core.InputSignal<boolean>;
4098
4128
  /** Control for the free-text "new flag" input. */
4099
- ft: FormControl;
4129
+ ft: FormControl<string | null>;
4100
4130
  /** Control for the known-flags dropdown; selecting a value adds it. */
4101
- fs: FormControl;
4131
+ fs: FormControl<string | null>;
4102
4132
  /** Flags currently on the model, rendered as badges. */
4103
4133
  curflags: _angular_core.WritableSignal<string[]>;
4104
4134
  /** Unused; kept for backwards compatibility. */
@@ -4114,9 +4144,9 @@ declare class FlagsComponent<T extends DataModel> implements OnInit {
4114
4144
  * from the model field, and wire the dropdown so that selecting a value adds
4115
4145
  * the flag (debounced 250ms).
4116
4146
  */
4117
- ngOnInit(): Promise<any>;
4147
+ ngOnInit(): void;
4118
4148
  /** Add the flag currently typed in the free-text input. */
4119
- addFreeFlag(): Promise<any>;
4149
+ addFreeFlag(): Promise<void>;
4120
4150
  /**
4121
4151
  * Add a flag and persist, unless it is already present.
4122
4152
  *
@@ -4125,12 +4155,12 @@ declare class FlagsComponent<T extends DataModel> implements OnInit {
4125
4155
  *
4126
4156
  * @param flag flag value to add.
4127
4157
  */
4128
- addFlag(flag: string): Promise<any>;
4158
+ addFlag(flag: string): Promise<void>;
4129
4159
  /**
4130
4160
  * Write the current flags back to the model field and save it, then reset
4131
4161
  * both inputs. Reports success or failure through the message service.
4132
4162
  */
4133
- update(): Promise<any>;
4163
+ update(): Promise<void>;
4134
4164
  /**
4135
4165
  * Whether the flag is already on the model.
4136
4166
  *
@@ -4143,7 +4173,7 @@ declare class FlagsComponent<T extends DataModel> implements OnInit {
4143
4173
  *
4144
4174
  * @param flag flag value to remove.
4145
4175
  */
4146
- removeFlag(flag: string): Promise<any>;
4176
+ removeFlag(flag: string): Promise<void>;
4147
4177
  /**
4148
4178
  * Switch between badge display and the editor.
4149
4179
  *
@@ -4175,9 +4205,7 @@ declare class SafeDeleteComponent<T extends DataModel> {
4175
4205
  success: boolean;
4176
4206
  }>) | undefined>;
4177
4207
  /** Translations of database objects names to human names */
4178
- checks: _angular_core.InputSignal<{
4179
- [index: string]: string;
4180
- }>;
4208
+ checks: _angular_core.InputSignal<Record<string, string>>;
4181
4209
  /** Messages to be displayed on success or error, default messages are used if not provided */
4182
4210
  messages: _angular_core.InputSignal<{
4183
4211
  success?: string;
@@ -4203,9 +4231,9 @@ declare class SafeDeleteComponent<T extends DataModel> {
4203
4231
  * First step: run the dry-run delete and open the confirmation panel with its
4204
4232
  * result. Nothing is destroyed here.
4205
4233
  *
4206
- * @param model unused; the component always acts on its own `[model]` input.
4234
+ * @param _model unused; the component always acts on its own `[model]` input.
4207
4235
  */
4208
- checkDelete(model: DataModel): Promise<void>;
4236
+ checkDelete(_model: DataModel): Promise<void>;
4209
4237
  /**
4210
4238
  * Second step: perform the actual deletion once the user has confirmed, then
4211
4239
  * close the panel and emit {@link deleted} with the dry-run result.
@@ -4456,7 +4484,7 @@ interface IDetailsFieldMetadata<T> extends IFieldMetadata<T> {
4456
4484
  * `forwardRef`, not a lambda). This is the real link of a relation: it is
4457
4485
  * what instantiates and populates the nested payload.
4458
4486
  */
4459
- model?: T;
4487
+ model?: DataModelType<DataModel>;
4460
4488
  /**
4461
4489
  * Custom deserialiser, taking precedence over `model`. Use it for payloads
4462
4490
  * that are not models — a plain shape, or a value needing transformation.
@@ -4531,7 +4559,7 @@ declare const reverseForeignKeyField: (updates: IReverseForeignKeyFieldMetadata)
4531
4559
  * @detailsField({ description: 'Labels', readonly: true })
4532
4560
  * public labels_details?: string[];
4533
4561
  */
4534
- declare const detailsField: (meta?: IDetailsFieldMetadata<any>) => FieldDecoratorFn;
4562
+ declare const detailsField: (meta?: IDetailsFieldMetadata<unknown>) => FieldDecoratorFn;
4535
4563
  /**
4536
4564
  * Declares the id half of a many-to-many relation: an array of ids, rendered
4537
4565
  * as a multi-select. Pairs with a `many: true` {@link detailsField} named
@@ -4710,7 +4738,7 @@ interface IComputedFieldMetadata<T> extends IFieldMetadata<T> {
4710
4738
  * }
4711
4739
  * }
4712
4740
  */
4713
- declare const computedField: (meta?: IComputedFieldMetadata<any>) => FieldDecoratorFn;
4741
+ declare const computedField: (meta?: IComputedFieldMetadata<unknown>) => FieldDecoratorFn;
4714
4742
 
4715
4743
  /** Severity of a {@link Message}; drives both the CSS classes and the default lifetime. */
4716
4744
  declare const enum TYPE {
@@ -4735,9 +4763,7 @@ declare enum STATUS {
4735
4763
  /**
4736
4764
  * Maps a sound key (`success`, `info`, `warning`, `error`, `notify`) to an audio file url.
4737
4765
  */
4738
- interface ISoundTypes {
4739
- [index: string]: string;
4740
- }
4766
+ type ISoundTypes = Record<string, string>;
4741
4767
  /**
4742
4768
  * Optional per-severity notification sounds ({@link ISoundTypes}) played by
4743
4769
  * {@link DataMessageService.play}.
@@ -4758,11 +4784,11 @@ declare class Message {
4758
4784
  title: string;
4759
4785
  type: TYPE;
4760
4786
  message: string;
4761
- trace: any;
4787
+ trace: unknown;
4762
4788
  ttl: number;
4763
4789
  status: string;
4764
4790
  showTrace: boolean;
4765
- constructor(title: string, type?: TYPE, message?: string, trace?: any, ttl?: number);
4791
+ constructor(title: string, type?: TYPE, message?: string, trace?: unknown, ttl?: number);
4766
4792
  /** Bootstrap alert classes matching this message's severity, bound by the message zone template. */
4767
4793
  get style(): string;
4768
4794
  /** 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 +4832,22 @@ declare class DataMessageService {
4806
4832
  */
4807
4833
  close(): void;
4808
4834
  /** 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;
4835
+ success(title: string, message?: string, trace?: unknown, timeout?: number): void;
4810
4836
  /** 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;
4837
+ info(title: string, message?: string, trace?: unknown, timeout?: number): void;
4812
4838
  /** 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;
4839
+ warning(title: string, message?: string, trace?: unknown, timeout?: number): void;
4814
4840
  /**
4815
4841
  * Reports a failure, with the longest lifetime (30 s) and the `error` sound.
4816
4842
  *
4817
4843
  * Note that the `timeout` argument is currently ignored: the message is always created
4818
4844
  * with {@link DEFAULT_TIMEOUTS.DANGER}.
4819
4845
  */
4820
- danger(title: string, message?: string, trace?: any, timeout?: number): void;
4846
+ danger(title: string, message?: string, trace?: unknown, timeout?: number): void;
4821
4847
  /** Alias for {@link danger}, for callers that think in terms of "error" rather than severity. */
4822
- error(title: string, message?: string, trace?: any): void;
4848
+ error(title: string, message?: string, trace?: unknown): void;
4823
4849
  /** 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;
4850
+ debug(title: string, message?: string, trace?: unknown): void;
4825
4851
  /**
4826
4852
  * Dismisses a message on the user's behalf and immediately re-emits the list, so the
4827
4853
  * message zone drops it without waiting for its ttl. Bound to the close button of
@@ -4925,13 +4951,13 @@ declare class QuerysetMock<T extends DataModel> {
4925
4951
  /** `[results, loading, meta]` combined, mirroring `Queryset.full`. */
4926
4952
  get full(): Observable<[T[], boolean, IQueryMeta]>;
4927
4953
  /** No-op returning `this`; the filter is discarded. See the class-level caveat. */
4928
- filter(params?: FilterData): QuerysetMock<T>;
4954
+ filter(_params?: FilterData): QuerysetMock<T>;
4929
4955
  /** No-op returning `this`; the ordering is discarded. See the class-level caveat. */
4930
- sort(...sorting: string[]): QuerysetMock<T>;
4956
+ sort(..._sorting: string[]): QuerysetMock<T>;
4931
4957
  /** No-op returning `this`; {@link pageSize} is left untouched. */
4932
- paginateBy(pageSize?: number): QuerysetMock<T>;
4958
+ paginateBy(_pageSize?: number): QuerysetMock<T>;
4933
4959
  /** No-op returning `this`; {@link page} is left untouched. */
4934
- setPage(page: number): QuerysetMock<T>;
4960
+ setPage(_page: number): QuerysetMock<T>;
4935
4961
  /** Always returns `{}`, since no query state is retained. */
4936
4962
  getQueryParams(): FilterData;
4937
4963
  /**
@@ -4993,13 +5019,13 @@ declare class CollectionMock<T extends DataModel> {
4993
5019
  * id, so a test that fetches two different ids gets the same instance back.
4994
5020
  * Reassign `mockValue` between calls if that matters.
4995
5021
  */
4996
- fetch(id: number, suffix?: string): Observable<T>;
5022
+ fetch(_id: number, _suffix?: string): Observable<T>;
4997
5023
  /**
4998
5024
  * Returns {@link actionValue}, ignoring the arguments. Since it does not record
4999
5025
  * the call, assert on the effects rather than on the invocation, or spy on this
5000
5026
  * method when you need to check what was requested.
5001
5027
  */
5002
- action(model: T | null, method: IHttpMethod, action: string, params: IActionParams): Observable<any>;
5028
+ action(_model: T | null, _method: IHttpMethod, _action: string, _params: IActionParams): Observable<any>;
5003
5029
  /**
5004
5030
  * Opens a {@link QuerysetMock} backed by this mock, mirroring
5005
5031
  * `Collection.queryset()`. With no `mockData` set on it, the queryset falls
@@ -5011,9 +5037,7 @@ declare class CollectionMock<T extends DataModel> {
5011
5037
  * real `Collection.list()` unwraps. Arguments are ignored, so filters are not
5012
5038
  * applied; set {@link mockValues} to whatever the filtered result should be.
5013
5039
  */
5014
- list(query?: {
5015
- [index: string]: string;
5016
- }, suffix?: string): Observable<any>;
5040
+ list(_query?: Record<string, string>, _suffix?: string): Observable<any>;
5017
5041
  /**
5018
5042
  * Builds real model instances from JSON, exactly as a live collection would —
5019
5043
  * this is the method you use to prepare {@link mockValue}/{@link mockValues}.
@@ -5027,7 +5051,7 @@ declare class CollectionMock<T extends DataModel> {
5027
5051
  * @param data JSON object or array of them
5028
5052
  * @param many set to `true` when `data` is an array
5029
5053
  */
5030
- fromJson(data: any, many?: boolean): T | any;
5054
+ fromJson(data: any, many?: boolean): any;
5031
5055
  }
5032
5056
 
5033
5057
  /**
@@ -5092,16 +5116,15 @@ interface UploadFile {
5092
5116
  /** Live status and throughput figures, updated as the transfer progresses. */
5093
5117
  progress: UploadProgress;
5094
5118
  /** Response body, parsed as JSON when possible and left as raw text otherwise. Set once done. */
5095
- response?: any;
5119
+ response?: unknown;
5096
5120
  /** HTTP status code of the response. Set once done — note that a 4xx/5xx still completes as `done`. */
5097
5121
  responseStatus?: number;
5098
- sub?: Subscription | any;
5122
+ /** Subscription of the in-flight request, used to cancel it. */
5123
+ sub?: Subscription;
5099
5124
  /** The underlying browser `File`, i.e. what is actually transferred. */
5100
5125
  nativeFile?: File;
5101
5126
  /** Response headers, parsed into a plain object. Set once done. */
5102
- responseHeaders?: {
5103
- [key: string]: string;
5104
- };
5127
+ responseHeaders?: Record<string, string>;
5105
5128
  }
5106
5129
  /**
5107
5130
  * An event emitted by the uploader on the `uploadOutput` output of the directives.
@@ -5112,7 +5135,7 @@ interface UploadFile {
5112
5135
  * `dragOver`/`dragOut`/`drop` for drop-zone hover states.
5113
5136
  */
5114
5137
  interface UploadOutput {
5115
- type: "addedToQueue" | "allAddedToQueue" | "uploading" | "done" | "start" | "cancelled" | "dragOver" | "dragOut" | "drop" | "removed" | "removedAll" | "rejected";
5138
+ type: 'addedToQueue' | 'allAddedToQueue' | 'uploading' | 'done' | 'start' | 'cancelled' | 'dragOver' | 'dragOut' | 'drop' | 'removed' | 'removedAll' | 'rejected';
5116
5139
  file?: UploadFile;
5117
5140
  nativeFile?: File;
5118
5141
  }
@@ -5124,7 +5147,7 @@ interface UploadOutput {
5124
5147
  * data); the queue-management commands only need `type` and, where relevant, `id`.
5125
5148
  */
5126
5149
  interface UploadInput {
5127
- type: "uploadAll" | "uploadFile" | "cancel" | "cancelAll" | "remove" | "removeAll";
5150
+ type: 'uploadAll' | 'uploadFile' | 'cancel' | 'cancelAll' | 'remove' | 'removeAll';
5128
5151
  /** Target endpoint for `uploadAll`/`uploadFile`. Defaults to an empty string, which posts to the current url. */
5129
5152
  url?: string;
5130
5153
  /** HTTP method to use; defaults to `POST`. */
@@ -5137,13 +5160,9 @@ interface UploadInput {
5137
5160
  /** The queued file to send with `uploadFile`; matched by identity against the queue. */
5138
5161
  file?: UploadFile;
5139
5162
  /** Extra form fields appended to the multipart body alongside the file. */
5140
- data?: {
5141
- [key: string]: string | Blob;
5142
- };
5163
+ data?: Record<string, string | Blob>;
5143
5164
  /** 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
- };
5165
+ headers?: Record<string, string>;
5147
5166
  includeWebKitFormBoundary?: boolean;
5148
5167
  /** Sends credentials (cookies) cross-origin; defaults to `false`. */
5149
5168
  withCredentials?: boolean;
@@ -5273,7 +5292,7 @@ declare class NgFileDropDirective implements OnInit, OnDestroy {
5273
5292
  ngOnDestroy(): void;
5274
5293
  stopEvent: (e: Event) => void;
5275
5294
  /** Handles a drop: emits `drop`, then screens and queues the dropped files. Bound to the host `drop` event. */
5276
- onDrop(e: any): void;
5295
+ onDrop(e: DragEvent): void;
5277
5296
  /** Emits `dragOver` while a drag hovers the zone, for highlighting it. Bound to the host `dragover` event. */
5278
5297
  onDragOver(e: Event): void;
5279
5298
  /** Emits `dragOut` when a drag leaves the zone, to clear the highlight. Bound to the host `dragleave` event. */
@@ -5286,7 +5305,7 @@ declare class NgFileSelectDirective implements OnInit, OnDestroy {
5286
5305
  /** Queue limits ({@link UploaderOptions}). Read once in `ngOnInit`; later changes have no effect. */
5287
5306
  options: _angular_core.InputSignal<UploaderOptions | undefined>;
5288
5307
  /** Command channel: emit {@link UploadInput}s on it to start, cancel or clear uploads. */
5289
- uploadInput: _angular_core.InputSignal<EventEmitter<any> | undefined>;
5308
+ uploadInput: _angular_core.InputSignal<EventEmitter<UploadInput> | undefined>;
5290
5309
  /** Queue and transfer events ({@link UploadOutput}) for this input. */
5291
5310
  uploadOutput: _angular_core.OutputEmitterRef<UploadOutput>;
5292
5311
  /** The queue backing this input, created in `ngOnInit`. */
@@ -5449,7 +5468,7 @@ declare const DATA_AUTH_PARAMS: InjectionToken<AuthParams>;
5449
5468
  * Using `useClass` here would give the auth layer a private instance whose cache and
5450
5469
  * state diverge from the one the rest of the application uses.
5451
5470
  */
5452
- declare const DATA_AUTH_USER_SERVICE: InjectionToken<Collection<any>>;
5471
+ declare const DATA_AUTH_USER_SERVICE: InjectionToken<Collection<DataModel>>;
5453
5472
  /**
5454
5473
  * Generic JWT authentication service, meant to be **extended** by the application.
5455
5474
  *
@@ -5495,11 +5514,6 @@ declare const DATA_AUTH_USER_SERVICE: InjectionToken<Collection<any>>;
5495
5514
  * @typeParam UserData - the decoded JWT payload, at least an {@link IJwtBaseData}.
5496
5515
  */
5497
5516
  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
5517
  /**
5504
5518
  * Emits every time {@link logout} runs, whether triggered by the user, by a failed
5505
5519
  * token refresh, or by a login error.
@@ -5514,7 +5528,13 @@ declare class AuthServiceBase<User extends DataModel, UserService extends Collec
5514
5528
  private _user$;
5515
5529
  private _token;
5516
5530
  private _fetched?;
5517
- constructor(userService: UserService, _params: AuthParams, _router: Router, _http: HttpClient, _msgs: DataMessageService);
5531
+ /** Collection used to fetch the logged-in user, from {@link DATA_AUTH_USER_SERVICE}. */
5532
+ userService: UserService;
5533
+ private _params;
5534
+ private _router;
5535
+ private _http;
5536
+ private _msgs;
5537
+ constructor();
5518
5538
  /** True when no access token is held, i.e. no session is active. Checked by {@link AuthInterceptor} before attaching a token. */
5519
5539
  get isAnonymous(): boolean;
5520
5540
  /** Current raw access token, or `null` when anonymous. Read by {@link AuthInterceptor} to build the `Authorization` header. */
@@ -5572,7 +5592,7 @@ declare class AuthServiceBase<User extends DataModel, UserService extends Collec
5572
5592
  * `'error'` when none is available), so callers can branch on the emitted string
5573
5593
  * rather than on an error callback.
5574
5594
  */
5575
- login(username: string, password: string): Observable<'success' | 'error' | 'invalid_token' | string>;
5595
+ login(username: string, password: string): Observable<'success' | 'error' | 'invalid_token' | (string & {})>;
5576
5596
  /**
5577
5597
  * Opens a session from an already-obtained token pair instead of credentials, for
5578
5598
  * flows where the tokens arrive out of band (SSO callback, impersonation, tests).
@@ -5580,7 +5600,7 @@ declare class AuthServiceBase<User extends DataModel, UserService extends Collec
5580
5600
  * Like {@link login}, it fetches the user, saves the session and reports failures as
5581
5601
  * an emitted code rather than an error.
5582
5602
  */
5583
- loginWithToken(accessToken: string, refreshToken: string): Observable<'success' | 'error' | 'invalid_token' | string>;
5603
+ loginWithToken(accessToken: string, refreshToken: string): Observable<'success' | 'error' | 'invalid_token' | (string & {})>;
5584
5604
  /**
5585
5605
  * Asks the backend to send an account reactivation token to the given user.
5586
5606
  *
@@ -5697,7 +5717,7 @@ declare class AuthInterceptor implements HttpInterceptor {
5697
5717
  * {@link DATA_AUTH_URLS}. Authenticated requests additionally get 401 handling with
5698
5718
  * automatic token refresh and replay.
5699
5719
  */
5700
- intercept(req: HttpRequest<any>, next: HttpHandler): rxjs.Observable<_angular_common_http.HttpEvent<any>>;
5720
+ intercept(req: HttpRequest<unknown>, next: HttpHandler): rxjs.Observable<_angular_common_http.HttpEvent<any>>;
5701
5721
  private _handle401Error;
5702
5722
  private _addToken;
5703
5723
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthInterceptor, never>;
@@ -5730,6 +5750,10 @@ interface BaseRouteParams {
5730
5750
  /**
5731
5751
  * Route function type.
5732
5752
  * Takes parameters, return a route.
5753
+ *
5754
+ * The parameters are `any` on purpose: a route builder is written against the
5755
+ * models it links to (`(product: Product) => [...]`), and such a function is not
5756
+ * assignable to one declared over `unknown[]`.
5733
5757
  */
5734
5758
  type RouteFn = (...args: any[]) => (string | number)[];
5735
5759
  /**
@@ -5753,18 +5777,14 @@ declare function RData<T extends BaseRouteParams>(value: T): T;
5753
5777
  * A map of route names to their {@link RouteConfigItem}, keyed by a string-literal union
5754
5778
  * so that every name is known statically and typos are compile errors.
5755
5779
  */
5756
- type RoutesConfig<T extends string> = {
5757
- readonly [Property in T]: RouteConfigItem;
5758
- };
5780
+ type RoutesConfig<T extends string> = Readonly<Record<T, RouteConfigItem>>;
5759
5781
  /** An Angular {@link Route} whose `data` is mandatory and typed as {@link BaseRouteParams}. */
5760
5782
  interface StrictRoutes extends Route {
5761
5783
  data: BaseRouteParams;
5762
5784
  }
5763
5785
  /** A route table built from {@link StrictRoutes}, i.e. where every route carries breadcrumb metadata. */
5764
5786
  type RoutesDefinition = StrictRoutes[];
5765
- interface RouteArg<T> {
5766
- [index: string]: T;
5767
- }
5787
+ type RouteArg<T> = Record<string, T>;
5768
5788
  /**
5769
5789
  * A bound, reusable reference to a route: it pairs a {@link RouteConfigItem} with a
5770
5790
  * context object from which the route's named parameters are read.
@@ -5787,7 +5807,7 @@ declare class Link<T> {
5787
5807
  * later. A missing `config` is tolerated (it warns and produces an inert link) so that
5788
5808
  * a partially-initialised template does not crash.
5789
5809
  */
5790
- constructor(config?: RouteConfigItem, context?: any, router?: Router);
5810
+ constructor(config?: RouteConfigItem, context?: object, router?: Router);
5791
5811
  private _name;
5792
5812
  /** Name of the underlying route config item, or an empty string when the link was built without config. */
5793
5813
  get name(): string;
@@ -5796,7 +5816,7 @@ declare class Link<T> {
5796
5816
  * context. Only the parameters declared by the route config are picked up; those
5797
5817
  * missing from `ctx` are reset to `null`, not left over from the previous context.
5798
5818
  */
5799
- context(ctx: any): void;
5819
+ context(ctx: object): void;
5800
5820
  /**
5801
5821
  * Builds the router commands array, resolving each declared parameter from `params`
5802
5822
  * first and falling back to the bound context.
@@ -5813,9 +5833,7 @@ declare class Link<T> {
5813
5833
  * Resolves to `false` without navigating when no router is available from either
5814
5834
  * source; otherwise it resolves with the router's own result.
5815
5835
  */
5816
- navigate(params?: {
5817
- [index: string]: T;
5818
- }, router?: Router): Promise<boolean>;
5836
+ navigate(params?: Record<string, T>, router?: Router): Promise<boolean>;
5819
5837
  }
5820
5838
 
5821
5839
  /** One rendered breadcrumb entry: its absolute path, label, icon classes, and whether it is the active leaf. */
@@ -5848,7 +5866,7 @@ declare class BreadcrumbComponent implements OnInit {
5848
5866
  /**
5849
5867
  * Additional data to be used in titleTemplate
5850
5868
  */
5851
- data: _angular_core.InputSignal<any>;
5869
+ data: _angular_core.InputSignal<object | undefined>;
5852
5870
  /**
5853
5871
  * Do we display icons ?
5854
5872
  */
@@ -5965,6 +5983,197 @@ declare class TabMemoryService {
5965
5983
  */
5966
5984
  declare const slugify: (str: string) => string;
5967
5985
 
5986
+ /**
5987
+ * Returns a document-unique DOM id, as `<prefix>-<n>`.
5988
+ *
5989
+ * Used to wire a `<label [attr.for]>` to the control its template owns: the
5990
+ * widgets in this library are instantiated repeatedly on the same page, so a
5991
+ * hard-coded id would collide and silently break the association.
5992
+ *
5993
+ * The counter is module-level and starts at zero in each JS context, so a
5994
+ * server-rendered page and its client hydration agree as long as components are
5995
+ * created in the same order — which is what Angular does.
5996
+ *
5997
+ * @param prefix identifies the widget, e.g. `data-filter`
5998
+ */
5999
+ declare function uniqueId(prefix: string): string;
6000
+
6001
+ /**
6002
+ * Teach jsdom the layout APIs CodeMirror and ProseMirror expect.
6003
+ *
6004
+ * Both editors measure the document to place the caret and decide what to
6005
+ * render, and jsdom has no layout engine: `getClientRects()` returns an empty
6006
+ * list and `elementFromPoint` does not exist at all. Zeroed rectangles are
6007
+ * enough — nothing in a spec depends on real geometry, it just has to not throw.
6008
+ *
6009
+ * Call it from `beforeEach` in any spec that instantiates an editor view. It is
6010
+ * idempotent, and only patches what is missing.
6011
+ */
6012
+ declare function installEditorDomShims(): void;
6013
+
6014
+ /**
6015
+ * The behaviour `<data-richedit>` and `<data-mdedit>` share with
6016
+ * `<data-dispedit>`: show the value, click to edit, save explicitly.
6017
+ *
6018
+ * It lives in the primary entry point so both editors can extend it — they
6019
+ * import `@solidev/data`, and the reverse would be a circular dependency
6020
+ * between entry points. Everything here is engine-agnostic: resolving which
6021
+ * field to read, which control to bind, when to show the editor, and how to
6022
+ * persist. The subclass owns its template and its editor widget.
6023
+ *
6024
+ * ## The source / rendered pair
6025
+ *
6026
+ * Long text is rarely one column. A markdown field is stored as its source
6027
+ * plus the HTML the server rendered from it, and this base follows that shape
6028
+ * by convention: given `field="description"` it edits `description_src` and
6029
+ * displays `description_html` **when the model declares them**, and falls back
6030
+ * to `description` for both when it does not. `[htmlField]` overrides the
6031
+ * display half outright. A `field` that already ends in `_src` is understood,
6032
+ * so `field="description_src"` resolves the same pair.
6033
+ */
6034
+ declare abstract class FieldEditorBase<FT, T extends DataModel> implements OnInit {
6035
+ /** Id tying the label to the editor. */
6036
+ readonly inputId: string;
6037
+ /** Id of the label element, wired to the editor through `aria-labelledby`. */
6038
+ readonly labelId: string;
6039
+ /** Model instance holding the field. */
6040
+ model: _angular_core.InputSignal<T | undefined>;
6041
+ /**
6042
+ * Field to edit, or the base name of a source / rendered pair.
6043
+ *
6044
+ * See the class description for how `<field>_src` and `<field>_html` are
6045
+ * resolved.
6046
+ */
6047
+ field: _angular_core.InputSignal<string | undefined>;
6048
+ /** Overrides the field read for the read-only display. */
6049
+ htmlField: _angular_core.InputSignal<string | undefined>;
6050
+ /** Whether {@link toggleEdit} is allowed to reveal the editor. */
6051
+ editable: _angular_core.InputSignal<boolean>;
6052
+ /**
6053
+ * Whether the editor is showing, in `dd` mode.
6054
+ *
6055
+ * Defaults to false: the value renders and a click reveals the editor, the
6056
+ * way `<data-dispedit>` behaves. `inline` and `form` modes ignore this and
6057
+ * follow {@link showEditor}.
6058
+ */
6059
+ edit: _angular_core.ModelSignal<boolean>;
6060
+ /**
6061
+ * Layout, and whether {@link save} persists.
6062
+ *
6063
+ * `dd` renders a `<dt>`/`<dd>` block and is the only mode that saves to the
6064
+ * API; `inline` and `form` render a label plus the editor and leave saving to
6065
+ * the caller.
6066
+ */
6067
+ mode: _angular_core.InputSignal<"inline" | "dd" | "form">;
6068
+ /** Hide label (for inline forms). */
6069
+ hideLabel: _angular_core.InputSignal<boolean>;
6070
+ /**
6071
+ * Hide the built-in save button, for callers driving persistence themselves
6072
+ * from the {@link changed} output.
6073
+ */
6074
+ hideButton: _angular_core.InputSignal<boolean>;
6075
+ /**
6076
+ * Form control backing the editor.
6077
+ *
6078
+ * When supplied, the component uses it as-is and skips its own setup — no
6079
+ * field manager lookup, no seeding from the model, and no {@link changed}
6080
+ * emissions. Takes precedence over `[form]`.
6081
+ */
6082
+ fc: _angular_core.InputSignal<FormControl<string | null> | undefined>;
6083
+ /**
6084
+ * External form group to register into, the way `<data-dispedit>` does.
6085
+ *
6086
+ * A control named after the resolved source field is added to it, seeded
6087
+ * from the model. Ignored when `[fc]` is supplied.
6088
+ */
6089
+ form: _angular_core.InputSignal<UntypedFormGroup | undefined>;
6090
+ /** Emits the value on every change. Not wired for a caller-supplied `[fc]`. */
6091
+ changed: _angular_core.OutputEmitterRef<string | null>;
6092
+ /** Field manager for the source field; only resolved when `fc` is created here. */
6093
+ manager?: BaseFieldManager<FT>;
6094
+ /** Whether the manager declares the field required. */
6095
+ readonly required: _angular_core.WritableSignal<boolean>;
6096
+ /** The control actually in use. */
6097
+ control: FormControl<string | null>;
6098
+ /** The base name of the pair, with any trailing `_src` removed. */
6099
+ private readonly baseField;
6100
+ /** The field actually edited. */
6101
+ readonly sourceField: _angular_core.Signal<string>;
6102
+ /** The field actually displayed in read-only mode. */
6103
+ readonly displayField: _angular_core.Signal<string>;
6104
+ /**
6105
+ * Whether the editor is showing rather than the rendered value.
6106
+ *
6107
+ * The rule is `<data-dispedit>`'s: `dd` follows `[edit]`, `inline` shows the
6108
+ * editor unless the field is not editable, and `form` always shows it — a
6109
+ * form control that renders as static text would be a surprise.
6110
+ */
6111
+ readonly showEditor: _angular_core.Signal<boolean>;
6112
+ /**
6113
+ * Resolve the control: the supplied `[fc]`, one registered into `[form]`, or
6114
+ * one of our own seeded from the model.
6115
+ */
6116
+ ngOnInit(): void;
6117
+ /**
6118
+ * Switch between the rendered value and the editor.
6119
+ * Does nothing when `[editable]` is false.
6120
+ */
6121
+ toggleEdit(): void;
6122
+ /**
6123
+ * The rendered value, as stored on the display field.
6124
+ *
6125
+ * Templates pass it through {@link SafeHtmlPipe} to inject it — **trusted,
6126
+ * not sanitized**, because Angular's sanitizer allow-list has no `style` and
6127
+ * would drop the alignment and colours the rich text editor writes. Point
6128
+ * these components at fields your own editor produced and your own API
6129
+ * stored.
6130
+ */
6131
+ displayValue(): string;
6132
+ /**
6133
+ * Write the editor content back to the model's source field, and persist it
6134
+ * in `dd` mode only.
6135
+ *
6136
+ * In `inline` / `form` modes the model is updated in memory but no request is
6137
+ * sent, leaving the save to the surrounding form. Does nothing without both a
6138
+ * `[model]` and a `[field]`.
6139
+ */
6140
+ save(): Promise<void>;
6141
+ /** Whether the model declares a property under that name. */
6142
+ private modelHas;
6143
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FieldEditorBase<any, any>, never>;
6144
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<FieldEditorBase<any, any>, never, never, { "model": { "alias": "model"; "required": false; "isSignal": true; }; "field": { "alias": "field"; "required": false; "isSignal": true; }; "htmlField": { "alias": "htmlField"; "required": false; "isSignal": true; }; "editable": { "alias": "editable"; "required": false; "isSignal": true; }; "edit": { "alias": "edit"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "hideLabel": { "alias": "hideLabel"; "required": false; "isSignal": true; }; "hideButton": { "alias": "hideButton"; "required": false; "isSignal": true; }; "fc": { "alias": "fc"; "required": false; "isSignal": true; }; "form": { "alias": "form"; "required": false; "isSignal": true; }; }, { "edit": "editChange"; "changed": "changed"; }, never, never, true, never>;
6145
+ }
6146
+
6147
+ /**
6148
+ * Marks HTML as trusted, so `[innerHTML]` renders it as-is.
6149
+ *
6150
+ * Meant for the rendered half of a rich text field — the `<field>_html` column
6151
+ * the server produces from the markdown or HTML source. Angular's sanitizer
6152
+ * builds its allow-list from `URI_ATTRS + HTML_ATTRS + ARIA_ATTRS`, and `style`
6153
+ * is in none of them, so a plain `[innerHTML]` silently drops every
6154
+ * `text-align` and every colour `<data-richedit>` writes: read-only mode ends
6155
+ * up looking nothing like the editor.
6156
+ *
6157
+ * **Only use it on HTML the server produced.** The value is inserted without
6158
+ * any filtering, and trusted HTML executes scripts — pointing this at a string
6159
+ * a user of another system can influence is an XSS vector.
6160
+ *
6161
+ * @example
6162
+ * ```html
6163
+ * <div [innerHTML]="product.description_html | safeHtml"></div>
6164
+ * ```
6165
+ */
6166
+ declare class SafeHtmlPipe implements PipeTransform {
6167
+ private _sanitizer;
6168
+ /**
6169
+ * @param value HTML to trust; null for a nullish or empty input, which
6170
+ * `[innerHTML]` renders as nothing.
6171
+ */
6172
+ transform(value: string | null | undefined): SafeHtml | null;
6173
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SafeHtmlPipe, never>;
6174
+ static ɵpipe: _angular_core.ɵɵPipeDeclaration<SafeHtmlPipe, "safeHtml", true>;
6175
+ }
6176
+
5968
6177
  /**
5969
6178
  * Compiles (and optionally renders) an Underscore-style string template, using ERB
5970
6179
  * delimiters: `<%= expr %>` interpolates, `<% code %>` evaluates, and a `print()` helper
@@ -5980,7 +6189,15 @@ declare const slugify: (str: string) => string;
5980
6189
  * Policy without `unsafe-eval`; and the escaping delimiter `<%- expr %>` is not usable,
5981
6190
  * as the code it emits calls `_.escape` while no `_` is in scope.
5982
6191
  */
5983
- declare const tmpl: (text: string, data?: any, objectName?: string) => any;
6192
+ /**
6193
+ * A template compiled by {@link tmpl} but not yet rendered: call it with the
6194
+ * data. `source` carries the generated JS, which is handy when debugging.
6195
+ */
6196
+ type CompiledTemplate = ((data: object) => string) & {
6197
+ source?: string;
6198
+ };
6199
+ declare function tmpl(text: string, data: object, objectName?: string): string;
6200
+ declare function tmpl(text: string, data?: undefined, objectName?: string): CompiledTemplate;
5984
6201
 
5985
6202
  /**
5986
6203
  * Watches the service worker for a newly deployed version and exposes it as a simple
@@ -5993,11 +6210,17 @@ declare const tmpl: (text: string, data?: any, objectName?: string) => any;
5993
6210
  * Harmless without a service worker: the checks fail and are logged as warnings.
5994
6211
  */
5995
6212
  declare class UpdateService {
6213
+ private stable;
5996
6214
  private appRef;
5997
6215
  private swUpdate;
5998
- private stable;
5999
- constructor(appRef: ApplicationRef, swUpdate: SwUpdate);
6216
+ constructor();
6000
6217
  private _available$;
6218
+ /**
6219
+ * Asks the service worker whether a new version is available. Swallows the
6220
+ * failure raised when no service worker is registered, which is the normal
6221
+ * case in development.
6222
+ */
6223
+ private _checkForUpdate;
6001
6224
  /**
6002
6225
  * Whether a new version is ready to be activated. Starts at `false`, becomes `true` on
6003
6226
  * `VERSION_READY`, and returns to `false` once the user activates or dismisses it.
@@ -6024,5 +6247,5 @@ declare class UpdateComponent {
6024
6247
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UpdateComponent, "data-update", never, {}, {}, never, never, true, never>;
6025
6248
  }
6026
6249
 
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 };
6250
+ 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, FieldEditorBase, 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, SafeHtmlPipe, 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 };
6251
+ 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 };