@nest-admin/nestjs 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1652 @@
1
+ import { ExecutionContext, DynamicModule, ModuleMetadata, FactoryProvider, Type } from '@nestjs/common';
2
+
3
+ /**
4
+ * Normalised, ORM-independent description of a model and its fields.
5
+ *
6
+ * Every ORM adapter translates its own schema representation (Prisma DMMF,
7
+ * TypeORM entity metadata, a Drizzle table object, ...) into these shapes.
8
+ * Nothing downstream - the CRUD engine, the HTTP API, the admin UI - is
9
+ * allowed to look at anything else.
10
+ *
11
+ * @experimental Draft contract. Expected to change during MVP implementation.
12
+ */
13
+ /** ORM-independent classification of a scalar or relation field. */
14
+ type FieldKind = 'string' | 'number' | 'boolean' | 'datetime' | 'enum' | 'json' | 'relation'
15
+ /** The adapter recognised the field but cannot map it onto a known kind. */
16
+ | 'unknown';
17
+ /** Cardinality of a relation from the owning model's point of view. */
18
+ type RelationCardinality = 'one' | 'many';
19
+ /**
20
+ * A relation, and how to act on it.
21
+ *
22
+ * `from` and `to` are what turn a relation from something an admin can only
23
+ * display into something it can filter and write. A to-one relation is stored
24
+ * as an ordinary scalar column - `Post.authorId` - and that column is what a
25
+ * query has to be expressed in terms of. Without knowing its name, a filter on
26
+ * `author` cannot be translated, and a form has no field to submit.
27
+ *
28
+ * Both are absent on to-many relations, which have no column on this side.
29
+ */
30
+ interface RelationMetadata {
31
+ /** `name` of the {@link ModelMetadata} on the other side of the relation. */
32
+ readonly targetModel: string;
33
+ readonly cardinality: RelationCardinality;
34
+ /**
35
+ * Scalar field on **this** model holding the foreign key, for a to-one
36
+ * relation - `authorId` on `Post.author`.
37
+ *
38
+ * Absent when the relation has no column on this side: every to-many, and
39
+ * the non-owning half of a one-to-one.
40
+ */
41
+ readonly from?: string;
42
+ /** Field on the target model that `from` points at - usually its id. */
43
+ readonly to?: string;
44
+ /**
45
+ * Name shared by both halves of the relation.
46
+ *
47
+ * The only reliable way to pair `User.posts` with `Post.author`, which two
48
+ * things need. Distinguishing a many-to-many from a one-to-many requires
49
+ * looking at the other side - both are `'many'` from here, but only one has
50
+ * no column anywhere. And knowing whether a child's key is required decides
51
+ * whether it can be detached at all.
52
+ *
53
+ * Two relations between the same pair of models are told apart by it too:
54
+ * `Post.author` and `Post.reviewer` both target `User`.
55
+ */
56
+ readonly name?: string;
57
+ }
58
+ interface FieldMetadata {
59
+ readonly name: string;
60
+ readonly kind: FieldKind;
61
+ /** Part of the model's primary key. */
62
+ readonly isId: boolean;
63
+ readonly isRequired: boolean;
64
+ readonly isUnique: boolean;
65
+ /** The field holds a list of {@link FieldKind} values. */
66
+ readonly isList: boolean;
67
+ /**
68
+ * The value is produced by the database or the ORM and is not asked of the
69
+ * user - `@default(cuid())`, `@default(now())`, `@default(autoincrement())`,
70
+ * `@updatedAt`. Such fields are displayed but not editable.
71
+ *
72
+ * This is NOT "has a default". A field with a literal default
73
+ * (`active Boolean @default(true)`) is an ordinary editable field that
74
+ * happens to arrive pre-filled; see {@link FieldMetadata.defaultValue}.
75
+ *
76
+ * NAME COLLISION - read before implementing an adapter. Prisma's DMMF also
77
+ * has a field called `isGenerated`, and it does NOT mean this. Measured
78
+ * against Prisma 7.10.0, DMMF reports `isGenerated: false` for
79
+ * `id String @id @default(cuid())`. Mapping it across directly produces
80
+ * editable primary keys. The correct derivation - a *function* default, or
81
+ * an updated-at column - is in `packages/prisma/src/metadata/to-metadata.ts`
82
+ * and `packages/drizzle/src/metadata/to-metadata.ts`, which state it in each
83
+ * ORM's own terms.
84
+ */
85
+ readonly isGenerated: boolean;
86
+ /**
87
+ * Accepted on a write, never returned on a read.
88
+ *
89
+ * Set by `writeOnly` in the configuration. A password is the reason it
90
+ * exists: it has to be typed into a form and must never come back out, and
91
+ * `hidden` cannot express that - it refuses the field in both directions, so
92
+ * a hidden password column leaves no way to set one.
93
+ *
94
+ * Enforced twice, deliberately: the field is left out of the columns the
95
+ * adapter is asked for, *and* out of the projection applied to whatever comes
96
+ * back. One of those is enough; two is what it takes for a future adapter
97
+ * that ignores the field scope not to become a leak.
98
+ */
99
+ readonly writeOnly?: boolean;
100
+ /**
101
+ * Literal default the admin should pre-fill on create, when the schema
102
+ * declares one (`@default(true)`, `@default(0)`, `@default("USER")`).
103
+ *
104
+ * Absent for generated values: there is no literal to pre-fill for
105
+ * `@default(now())`, and {@link FieldMetadata.isGenerated} is `true` instead.
106
+ */
107
+ readonly defaultValue?: unknown;
108
+ /** Populated when `kind` is `'enum'`. */
109
+ readonly enumValues?: readonly string[];
110
+ /** Populated when `kind` is `'relation'`. */
111
+ readonly relation?: RelationMetadata;
112
+ }
113
+ interface ModelMetadata {
114
+ /** Adapter-facing identifier, e.g. the Prisma model name `User`. */
115
+ readonly name: string;
116
+ /**
117
+ * Field names forming the primary key. Modelled as a list rather than a
118
+ * single `id` so composite keys do not require a breaking change later,
119
+ * even though the MVP will only support single-column keys.
120
+ */
121
+ readonly primaryKey: readonly string[];
122
+ readonly fields: readonly FieldMetadata[];
123
+ /**
124
+ * Field that names a record of this model in one line, when the application
125
+ * has declared one.
126
+ *
127
+ * A slot rather than a value: left unset, `displayFieldFor` works it out from
128
+ * the fields. It is here so that a declared choice travels with the model and
129
+ * reaches the adapter and the metadata document alike, without either of them
130
+ * having to read configuration.
131
+ */
132
+ readonly displayField?: string;
133
+ }
134
+
135
+ /**
136
+ * Per-model and per-field configuration.
137
+ *
138
+ * The schema says what a model *is*; this says how the admin should treat it.
139
+ * Two different questions, so they are two different inputs - a column being a
140
+ * string is a fact about the database, and that column being a password is a
141
+ * fact about the application.
142
+ *
143
+ * The overrides divide into two kinds, and the difference matters:
144
+ *
145
+ * behaviour `hidden`, `readOnly`, `displayField`. Enforced. A hidden
146
+ * field is removed from the metadata every layer reads, so it
147
+ * cannot be filtered, sorted, written or returned - see
148
+ * `applyOverrides`.
149
+ *
150
+ * behaviour `writeOnly` too - accepted on a write and stripped from
151
+ * every read.
152
+ *
153
+ * presentation `label`, `widget`, `order`. Passed to the client, which is
154
+ * free to ignore them. Nothing depends on them being honoured.
155
+ *
156
+ * Anything in the first group that were only presentation would be a security
157
+ * hole with a reassuring name.
158
+ */
159
+
160
+ /**
161
+ * How a field should be edited, when its type does not say enough.
162
+ *
163
+ * A `string` column may be a sentence, a password, an address or a colour, and
164
+ * the schema cannot tell them apart. Deliberately a closed list: a client has
165
+ * to know how to render each one, so an open string would mean silently
166
+ * falling back to a plain input and no way to notice.
167
+ */
168
+ type FieldWidget = 'textarea' | 'password' | 'email' | 'url' | 'color' | 'json';
169
+ interface FieldOverride {
170
+ /**
171
+ * Remove the field from the admin entirely.
172
+ *
173
+ * **Enforced, not cosmetic.** The field is dropped from the metadata before
174
+ * anything reads it, so it is absent from the schema document, rejected in
175
+ * filters and sorts, refused in writes, and stripped from every response.
176
+ * A password hash is the reason this exists.
177
+ */
178
+ readonly hidden?: boolean;
179
+ /** Show the field, refuse to write it. Generated columns are already this. */
180
+ readonly readOnly?: boolean;
181
+ /**
182
+ * Write the field, never read it back. The mirror of `readOnly`.
183
+ *
184
+ * **Enforced, not cosmetic.** The column is left out of the query the adapter
185
+ * makes and out of the projection applied to the result, so it is absent from
186
+ * a list, from a detail page and from the record a write returns - while
187
+ * still being accepted in the write itself.
188
+ *
189
+ * A password is what this is for. `hidden` is the wrong tool: it refuses the
190
+ * field in both directions, so a hidden password column can never be set.
191
+ */
192
+ readonly writeOnly?: boolean;
193
+ /** What to call it, when the column name is not what people call the thing. */
194
+ readonly label?: string;
195
+ /** How to edit it. See {@link FieldWidget}. */
196
+ readonly widget?: FieldWidget;
197
+ /** Where it sits among the others. Lower comes first; unset comes last. */
198
+ readonly order?: number;
199
+ }
200
+ /**
201
+ * Icons a model may be given in the navigation.
202
+ *
203
+ * A closed list, for the same reason `FieldWidget` is one: the interface has to
204
+ * know how to draw each name, so an open string would mean silently rendering
205
+ * nothing and no way to notice. It is also a bundle decision - the icon set has
206
+ * about fifteen hundred entries, and only the ones named here are shipped.
207
+ *
208
+ * Chosen to cover what an admin's resources usually are rather than to be
209
+ * complete. A model with no icon is drawn without one, which is the default and
210
+ * is not a lesser state: identical icons down a column are decoration, and the
211
+ * navigation reads better with none than with thirty of the same shape.
212
+ */
213
+ type ModelIcon = 'users' | 'user' | 'building' | 'box' | 'package' | 'tag' | 'shopping-cart' | 'credit-card' | 'receipt' | 'file-text' | 'folder' | 'image' | 'calendar' | 'clock' | 'mail' | 'message-square' | 'bell' | 'star' | 'map-pin' | 'globe' | 'settings' | 'key' | 'shield' | 'database' | 'table' | 'layers' | 'list' | 'chart-bar' | 'activity' | 'truck' | 'gift' | 'bookmark' | 'link';
214
+ interface ModelOverride {
215
+ /** What to call the model. */
216
+ readonly label?: string;
217
+ /**
218
+ * Which icon to show beside it in the navigation.
219
+ *
220
+ * Presentational: the client may ignore it, and nothing depends on it being
221
+ * honoured. See {@link ModelIcon} for why the list is closed.
222
+ */
223
+ readonly icon?: ModelIcon;
224
+ /**
225
+ * Which field names a record, overriding what would be detected.
226
+ *
227
+ * The detection rule guesses well on conventional schemas and has no way to
228
+ * know that a `code` column is the one people recognise.
229
+ */
230
+ readonly displayField?: string;
231
+ /** Where the model sits in the resource list. Lower first; unset last. */
232
+ readonly order?: number;
233
+ readonly fields?: Readonly<Record<string, FieldOverride>>;
234
+ }
235
+ type ModelOverrides = Readonly<Record<string, ModelOverride>>;
236
+
237
+ /**
238
+ * Where admin accounts live, as a contract.
239
+ *
240
+ * ## Why this exists at all
241
+ *
242
+ * Until 0.9.0 the answer to "who may open the admin?" was always the host
243
+ * application's: it already had sessions, and `AdminAuth` asked it one
244
+ * question. That is still right for a team that has an identity system, and
245
+ * nothing about it changes.
246
+ *
247
+ * It is a wall for everyone else. An application with no login of its own had
248
+ * to write a password hash, a session cookie and a form before the admin could
249
+ * go anywhere near production - which is a strange thing to ask of a package
250
+ * whose whole claim is that you do not build an admin.
251
+ *
252
+ * ## Why it is a contract rather than a table
253
+ *
254
+ * The same reason `OrmAdapter` is. An admin whose accounts can only live in
255
+ * Prisma has learned about Prisma, and the second ORM would find out the hard
256
+ * way. Everything here is plain data and promises; nothing knows what a
257
+ * database is.
258
+ *
259
+ * ## These accounts are not the application's users
260
+ *
261
+ * Deliberately, and this is the point most worth getting right. The people who
262
+ * administer a system are usually not rows in the table they administer, and
263
+ * conflating the two means a customer record with a password that opens the
264
+ * admin. The store is separate storage - a different model, or a different
265
+ * database entirely - and the admin never reads or writes the application's
266
+ * own users to decide who may sign in.
267
+ */
268
+ /** One account that may sign in to the admin. */
269
+ interface AdminAccount {
270
+ readonly id: string;
271
+ /**
272
+ * What is typed into the login form.
273
+ *
274
+ * Called `email` because that is what it almost always is, and a name people
275
+ * recognise is worth more than one that covers a case nobody has. A store is
276
+ * free to hold usernames in it.
277
+ */
278
+ readonly email: string;
279
+ /** Shown in the interface. Falls back to the email when absent. */
280
+ readonly name?: string | undefined;
281
+ /**
282
+ * The stored password hash, in whatever form the hasher produced.
283
+ *
284
+ * Read by the sign-in check and by nothing else. It must never reach a
285
+ * response, and the account the interface is told about is a projection that
286
+ * does not include it.
287
+ */
288
+ readonly passwordHash: string;
289
+ /**
290
+ * Suspended without being deleted.
291
+ *
292
+ * Distinct from removing the row: an account that has done things is worth
293
+ * keeping for the record, and "cannot sign in" is not the same fact as
294
+ * "never existed".
295
+ */
296
+ readonly disabled?: boolean | undefined;
297
+ }
298
+ /**
299
+ * The account as the interface may see it.
300
+ *
301
+ * A separate type rather than a comment on {@link AdminAccount}, because "do
302
+ * not send the hash" is a rule that gets forgotten and a type that cannot
303
+ * carry it does not.
304
+ */
305
+ interface AdminAccountSummary {
306
+ readonly id: string;
307
+ readonly email: string;
308
+ readonly name?: string | undefined;
309
+ }
310
+ /**
311
+ * How the admin reaches its accounts.
312
+ *
313
+ * Read-only by design. Creating and editing accounts is the application's
314
+ * business: it owns the storage, it knows whether that is a migration, a seed
315
+ * script or a form somewhere else, and an admin that could mint its own
316
+ * administrators is an escalation waiting for its first mistake.
317
+ */
318
+ interface AdminAccountStore {
319
+ /**
320
+ * Find an account by what was typed into the login form.
321
+ *
322
+ * Matching is the store's decision, and it should be case-insensitive on the
323
+ * local part in practice: someone who registered as `Ada@example.com` will
324
+ * type `ada@example.com` eventually.
325
+ *
326
+ * Returns `null` when there is none. The caller must not behave observably
327
+ * differently for `null` than for a wrong password - see the sign-in code.
328
+ */
329
+ findByEmail(email: string): Promise<AdminAccount | null>;
330
+ /**
331
+ * Find an account by its id, for a request that arrives with a session.
332
+ *
333
+ * Called on every authenticated request, so it should be cheap. It is also
334
+ * what makes a disabled or deleted account stop working immediately rather
335
+ * than when its session happens to expire.
336
+ */
337
+ findById(id: string): Promise<AdminAccount | null>;
338
+ /**
339
+ * How many accounts exist.
340
+ *
341
+ * Used once, at startup, to say so when the answer is zero - an admin nobody
342
+ * can sign in to is a configuration mistake that otherwise announces itself
343
+ * as a login form that rejects everything.
344
+ */
345
+ count(): Promise<number>;
346
+ /**
347
+ * Note that an account signed in. Optional.
348
+ *
349
+ * A store that does not care about this can leave it out; the sign-in path
350
+ * does not wait for it and a failure is logged rather than surfaced, because
351
+ * "your login worked but we could not write down that it did" is not
352
+ * something the person signing in can act on.
353
+ */
354
+ recordLogin?(id: string): Promise<void>;
355
+ /**
356
+ * What this store reads, for diagnostics. Optional.
357
+ *
358
+ * A model name, a table, a directory - whatever names the storage in a way a
359
+ * person would recognise. It exists so a startup check can say something
360
+ * useful rather than something generic: an admin that exposes its own
361
+ * account model as an editable resource is an escalation, and a warning that
362
+ * cannot name the model is a warning nobody acts on.
363
+ *
364
+ * Never used to decide anything, and never sent to a client.
365
+ */
366
+ readonly describes?: string;
367
+ }
368
+
369
+ /**
370
+ * ORM-independent query description.
371
+ *
372
+ * The admin UI and the HTTP layer speak only this vocabulary; each adapter is
373
+ * responsible for translating it into its own query language.
374
+ *
375
+ * @experimental Draft contract. Expected to change during MVP implementation.
376
+ */
377
+ type SortDirection = 'asc' | 'desc';
378
+ interface SortRule {
379
+ readonly field: string;
380
+ readonly direction: SortDirection;
381
+ }
382
+ /**
383
+ * The deliberately small operator set the MVP targets. Anything richer
384
+ * (nested relation filters, OR/AND trees, full-text) is a later concern and
385
+ * should extend this union rather than bypass it.
386
+ */
387
+ type FilterOperator = 'eq' | 'ne' | 'contains' | 'startsWith' | 'endsWith' | 'gt' | 'gte' | 'lt' | 'lte' | 'in';
388
+ interface FilterRule {
389
+ readonly field: string;
390
+ readonly operator: FilterOperator;
391
+ readonly value: unknown;
392
+ }
393
+ /** Page-number based pagination. Cursor pagination is a later addition. */
394
+ interface ListQuery {
395
+ readonly page?: number;
396
+ readonly perPage?: number;
397
+ readonly sort?: readonly SortRule[];
398
+ readonly filters?: readonly FilterRule[];
399
+ /** Free-text term the adapter applies across searchable string fields. */
400
+ readonly search?: string;
401
+ /**
402
+ * The fields this query may touch, and the only ones it should return.
403
+ *
404
+ * Set by the caller that knows which fields the admin exposes - the adapter
405
+ * reads a schema, not a configuration. Without it, a field the application
406
+ * hid would still be searched by free text, sortable, filterable and
407
+ * returned, because from the adapter's side it is an ordinary column.
408
+ *
409
+ * Omitted means "every field the model has".
410
+ */
411
+ readonly fields?: readonly string[];
412
+ }
413
+ interface Page<T> {
414
+ readonly data: readonly T[];
415
+ readonly total: number;
416
+ readonly page: number;
417
+ readonly perPage: number;
418
+ }
419
+
420
+ /**
421
+ * The single seam between Nest Admin and any ORM.
422
+ *
423
+ * Adding support for a new ORM means writing one implementation of
424
+ * {@link OrmAdapter} and nothing else. Core, the NestJS integration, the HTTP
425
+ * contract and the admin UI stay untouched.
426
+ *
427
+ * @experimental Draft contract. Expected to change during MVP implementation.
428
+ */
429
+
430
+ /**
431
+ * Primary key value of a single record. Composite keys are represented by
432
+ * {@link ModelMetadata.primaryKey}; supporting them at this level is a
433
+ * post-MVP change.
434
+ */
435
+ type RecordId = string | number;
436
+ /** An untyped record as it crosses the adapter boundary. */
437
+ type RecordData = Record<string, unknown>;
438
+ interface OrmAdapter {
439
+ /** Stable identifier used in diagnostics, e.g. `'prisma'`. */
440
+ readonly name: string;
441
+ /**
442
+ * Discover the models the adapter can serve. Asynchronous because an adapter
443
+ * may need to read a schema file or import a generated client.
444
+ */
445
+ getModels(): Promise<readonly ModelMetadata[]>;
446
+ list(model: string, query: ListQuery): Promise<Page<RecordData>>;
447
+ findOne(model: string, id: RecordId): Promise<RecordData | null>;
448
+ create(model: string, data: RecordData): Promise<RecordData>;
449
+ update(model: string, id: RecordId, data: RecordData): Promise<RecordData>;
450
+ delete(model: string, id: RecordId): Promise<void>;
451
+ /**
452
+ * A page of the records on the far side of a to-many relation.
453
+ *
454
+ * Paginated for the same reason a list is: the number of children is a
455
+ * property of the data, not of the schema, and a parent with fifty thousand
456
+ * of them must not be a page that never loads.
457
+ *
458
+ * Kept separate from `list` rather than expressed as a filter because a
459
+ * many-to-many has no column to filter on - the link lives in a join table.
460
+ * A one-to-many could be asked for either way; going through one method means
461
+ * the caller does not have to know which it is looking at.
462
+ */
463
+ listRelated(model: string, id: RecordId, relationField: string, query: ListQuery): Promise<Page<RecordData>>;
464
+ /**
465
+ * Link an existing record to this one.
466
+ *
467
+ * Across a many-to-many this adds a row to the join table and changes
468
+ * neither record. Across a one-to-many it rewrites the child's foreign key,
469
+ * which also **removes it from whatever parent held it** - the same operation
470
+ * with a consequence the caller should have been told about. Deciding whether
471
+ * to warn is the transport layer's job; the adapter performs what it is asked.
472
+ */
473
+ attachRelated(model: string, id: RecordId, relationField: string, targetId: RecordId): Promise<void>;
474
+ /**
475
+ * Unlink a record from this one, without deleting either.
476
+ *
477
+ * Across a one-to-many this clears the child's foreign key, which is
478
+ * impossible when that column is required - see `detachBlockedReason`. The
479
+ * adapter may assume the caller has checked, and will surface the database's
480
+ * own refusal if it has not.
481
+ */
482
+ detachRelated(model: string, id: RecordId, relationField: string, targetId: RecordId): Promise<void>;
483
+ }
484
+
485
+ /**
486
+ * Which models the admin exposes at all.
487
+ *
488
+ * Distinct from resource authorization, and the two answer different questions.
489
+ * A `ResourceSelection` is structural: it decides what the admin *is*, the same
490
+ * for everyone, and a model outside it does not exist as far as the admin is
491
+ * concerned. `AdminResourceAuth` is per-principal: the model exists, and this
492
+ * caller may or may not act on it.
493
+ *
494
+ * That difference is visible in the response. An excluded model answers 404 -
495
+ * there is no such resource - where a denied one answers 403.
496
+ */
497
+ interface ResourceSelection {
498
+ /**
499
+ * When present, only these models are exposed. Everything else is dropped,
500
+ * including models added to the schema later - which is the point: an
501
+ * allow-list does not quietly grow when someone edits the schema.
502
+ */
503
+ readonly include?: readonly string[];
504
+ /**
505
+ * Models removed from the selection, applied after `include`.
506
+ *
507
+ * The usual reason is a table that is not domain data: session stores,
508
+ * migration bookkeeping, queue tables.
509
+ */
510
+ readonly exclude?: readonly string[];
511
+ }
512
+
513
+ /**
514
+ * Framework error vocabulary.
515
+ *
516
+ * Deliberately small. These exist so that adapters raise ORM-independent
517
+ * errors and the transport layer can map them to status codes without knowing
518
+ * which ORM produced them. Resist growing this taxonomy - add a new type only
519
+ * when a caller genuinely needs to branch on it.
520
+ *
521
+ * ## Why these are not identified with `instanceof`
522
+ *
523
+ * A published bundle can contain more than one copy of this module. The
524
+ * package ships two CommonJS entrypoints and each inlines its own copy of
525
+ * Core, so an error thrown inside the Prisma adapter is an instance of a
526
+ * *different* `FieldNotFoundError` class than the one the exception filter
527
+ * holds. `instanceof` compares class identity, so it answered `false` and
528
+ * every adapter-raised error was mapped to a generic 500 - a caller who
529
+ * mistyped a sort field got "internal error" instead of "unknown field".
530
+ *
531
+ * That was invisible to this repository's own tests, which resolve Core to a
532
+ * single source module, and only appeared when the built package was installed
533
+ * and run. So errors are identified by *value* rather than identity: a
534
+ * `Symbol.for` brand, which duplicate copies agree on by definition, plus a
535
+ * stable `kind` string. Neither depends on which copy created the object.
536
+ *
537
+ * `scripts/verify-packed-consumer.mjs` asserts the arrangement every release:
538
+ * one shared copy in ESM, one per entrypoint in CJS. If that ever changes, the
539
+ * count changes there first.
540
+ *
541
+ * @experimental Draft contract. Expected to change during MVP implementation.
542
+ */
543
+ /**
544
+ * Stable discriminator for each error type.
545
+ *
546
+ * A declared string rather than the class, so it survives duplicate bundles,
547
+ * and rather than `name`, so it survives minification.
548
+ */
549
+ type AdminErrorKind = 'unauthorized' | 'forbidden' | 'model-not-found' | 'field-not-found' | 'record-not-found' | 'invalid-query'
550
+ /** Application code refused the input. Its message reaches the client. */
551
+ | 'validation'
552
+ /** The database refused the write: unique, foreign key, or required. */
553
+ | 'constraint' | 'adapter'
554
+ /** A subclass that declared no kind of its own. Treated as internal. */
555
+ | 'unknown';
556
+ /**
557
+ * Base error type. Every error raised by Nest Admin extends it so that the
558
+ * NestJS integration can distinguish framework errors from application errors
559
+ * without depending on concrete subclasses.
560
+ */
561
+ declare class NestAdminError extends Error {
562
+ /**
563
+ * Which error this is.
564
+ *
565
+ * Subclasses override it with a literal. The base value covers anything that
566
+ * extends this class without declaring one - the Prisma schema errors, for
567
+ * instance - which the transport layer treats as internal.
568
+ */
569
+ readonly kind: AdminErrorKind;
570
+ constructor(message: string, options?: {
571
+ cause?: unknown;
572
+ });
573
+ }
574
+ /**
575
+ * Is this one of ours?
576
+ *
577
+ * Works across duplicate copies of this module, which `instanceof` does not.
578
+ */
579
+ declare function isNestAdminError(value: unknown): value is NestAdminError;
580
+ /** The requested model is not part of the admin's resource set. */
581
+ declare class ModelNotFoundError extends NestAdminError {
582
+ readonly model: string;
583
+ readonly availableModels: readonly string[];
584
+ readonly kind: "model-not-found";
585
+ constructor(model: string, availableModels?: readonly string[]);
586
+ }
587
+ /** A referenced field does not exist on the model, or cannot be used this way. */
588
+ declare class FieldNotFoundError extends NestAdminError {
589
+ readonly model: string;
590
+ readonly field: string;
591
+ readonly kind: "field-not-found";
592
+ constructor(model: string, field: string, reason?: string);
593
+ }
594
+ /** No record matched the given identifier. */
595
+ declare class RecordNotFoundError extends NestAdminError {
596
+ readonly model: string;
597
+ readonly id: unknown;
598
+ readonly kind: "record-not-found";
599
+ constructor(model: string, id: unknown);
600
+ }
601
+ /**
602
+ * The query is structurally invalid - an unusable operator/field combination,
603
+ * a malformed value, or a request the adapter cannot express.
604
+ */
605
+ declare class InvalidQueryError extends NestAdminError {
606
+ readonly kind: "invalid-query";
607
+ }
608
+ /**
609
+ * The input is not acceptable, and the caller should be told why.
610
+ *
611
+ * Raised by application code - a hook rejecting a value, a rule the schema
612
+ * cannot express - rather than by the framework. It exists because such a
613
+ * refusal has to reach the person who typed the value, and the alternatives are
614
+ * wrong in one direction or the other: `InvalidQueryError` claims the *query*
615
+ * was malformed, and anything unrecognised becomes a generic 500 with the
616
+ * message withheld.
617
+ *
618
+ * The message **is** forwarded to the client, which is the point of it and also
619
+ * the responsibility that comes with it: whatever goes in is published.
620
+ *
621
+ * Naming the fields it is about is optional and worth doing. An interface that
622
+ * knows which input was refused can say so next to that input, where the person
623
+ * is looking, instead of in a banner above a form they then have to re-read.
624
+ */
625
+ declare class ValidationError extends NestAdminError {
626
+ /** The inputs this is about. Empty when it is about the record as a whole. */
627
+ readonly fields: readonly string[];
628
+ readonly kind: "validation";
629
+ constructor(message: string,
630
+ /** The inputs this is about. Empty when it is about the record as a whole. */
631
+ fields?: readonly string[], options?: {
632
+ cause?: unknown;
633
+ });
634
+ }
635
+ /**
636
+ * What the database refused, and about which fields.
637
+ *
638
+ * The distinction that matters is between a request that is *wrong* and a
639
+ * database that is *broken*. A duplicate email, a foreign key pointing at
640
+ * nothing, a missing required value - these are ordinary mistakes a person
641
+ * makes in a form, and until they were told apart from a real failure the admin
642
+ * answered every one of them with "an internal error occurred".
643
+ *
644
+ * The message is built here, from the constraint and the field names, rather
645
+ * than taken from the ORM. An ORM's own text carries file paths, generated
646
+ * query fragments and the values that collided, none of which should be
647
+ * published - which is exactly why the generic 500 existed in the first place.
648
+ */
649
+ type ConstraintKind =
650
+ /** A value that has to be unique is not. */
651
+ 'unique'
652
+ /** A reference points at a record that is not there, or is still referenced. */
653
+ | 'foreign-key'
654
+ /** A value the database requires was not supplied. */
655
+ | 'required';
656
+ declare class ConstraintError extends NestAdminError {
657
+ readonly constraint: ConstraintKind;
658
+ readonly model: string;
659
+ /** The columns involved. Empty when the ORM did not say. */
660
+ readonly fields: readonly string[];
661
+ readonly kind: "constraint";
662
+ constructor(constraint: ConstraintKind, model: string,
663
+ /** The columns involved. Empty when the ORM did not say. */
664
+ fields?: readonly string[]);
665
+ }
666
+ /**
667
+ * The underlying ORM or database failed. Always wraps the original error as
668
+ * `cause` so the real failure is never lost.
669
+ */
670
+ declare class AdapterError extends NestAdminError {
671
+ readonly kind: "adapter";
672
+ constructor(message: string, options?: {
673
+ cause?: unknown;
674
+ });
675
+ }
676
+ /**
677
+ * No authenticated identity was presented with the request.
678
+ *
679
+ * Raised by the host application's admin auth implementation, never by Core
680
+ * itself - Core has no notion of a request, a header or a session, and must
681
+ * not acquire one. It exists here so the transport layer can map it without
682
+ * knowing which framework produced it.
683
+ *
684
+ * The default message is deliberately uninformative. An authentication failure
685
+ * must not reveal whether a credential was absent, malformed, expired or
686
+ * simply wrong.
687
+ */
688
+ declare class UnauthorizedError extends NestAdminError {
689
+ readonly kind: "unauthorized";
690
+ constructor(message?: string);
691
+ }
692
+ /**
693
+ * An identity was established, but it is not permitted to do this.
694
+ *
695
+ * Deliberately distinct from {@link UnauthorizedError}: collapsing the two
696
+ * leaves a client unable to tell "log in" from "you cannot do this", and
697
+ * pushes that guesswork into every consumer.
698
+ */
699
+ declare class ForbiddenError extends NestAdminError {
700
+ readonly kind: "forbidden";
701
+ constructor(message?: string);
702
+ }
703
+
704
+ /**
705
+ * Buttons the application adds.
706
+ *
707
+ * CRUD covers what a schema implies. It does not cover "publish", "resend the
708
+ * invitation", "recalculate the total" - operations that are obvious to the
709
+ * domain and invisible to the database. Without somewhere to put them, the
710
+ * usual answer is a second internal tool beside the admin.
711
+ *
712
+ * An action is defined once and drawn by the interface from metadata, so adding
713
+ * one is a server-side change and needs no rebuild of the UI.
714
+ *
715
+ * ## Authorization
716
+ *
717
+ * Actions are a distinct operation, `'action'`, rather than folded into
718
+ * `update`. An action can do anything, including things no CRUD route offers,
719
+ * so a policy should be able to decide about it separately - and a policy
720
+ * written before actions existed denies the unfamiliar value, which is the
721
+ * right direction to fail in.
722
+ *
723
+ * Actions the principal may not run are absent from the metadata, so the
724
+ * interface does not draw a button that would be refused.
725
+ */
726
+
727
+ /** What an action reports back. */
728
+ interface AdminActionResult {
729
+ /**
730
+ * A sentence for the person who pressed the button.
731
+ *
732
+ * Shown as-is, so it is published - the same responsibility as a
733
+ * `ValidationError` message.
734
+ */
735
+ readonly message?: string;
736
+ }
737
+ interface AdminAction {
738
+ /** Stable identifier, used in the URL. Letters, digits, `-` and `_`. */
739
+ readonly name: string;
740
+ /** What the button says. Defaults to `name`. */
741
+ readonly label?: string;
742
+ /**
743
+ * Whether the action applies to one record or to the model as a whole.
744
+ *
745
+ * A `'record'` action is offered on the detail page and receives the record's
746
+ * id; a `'list'` action is offered above the list and receives none.
747
+ */
748
+ readonly scope: 'record' | 'list';
749
+ /**
750
+ * Ask before running, with this as the question.
751
+ *
752
+ * Worth setting for anything that cannot be undone. The interface refuses to
753
+ * proceed without an answer; it is not a substitute for the server checking
754
+ * that the action is permitted.
755
+ */
756
+ readonly confirm?: string;
757
+ /** Draw the button as destructive. Presentation only. */
758
+ readonly danger?: boolean;
759
+ /**
760
+ * The work.
761
+ *
762
+ * Throw to refuse: a `ValidationError` reaches the caller with its message,
763
+ * anything else becomes a 500 with the message withheld.
764
+ */
765
+ readonly run: (args: {
766
+ readonly context: ExecutionContext;
767
+ readonly model: string;
768
+ /** Present for a `'record'` action, absent for a `'list'` one. */
769
+ readonly id?: RecordId;
770
+ }) => AdminActionResult | void | Promise<AdminActionResult | void>;
771
+ }
772
+ /** Actions per model. Models without an entry have none. */
773
+ type AdminActionsByModel = Readonly<Record<string, readonly AdminAction[]>>;
774
+
775
+ /**
776
+ * Application code that runs around a write.
777
+ *
778
+ * The admin is generic on purpose: it knows a schema, not a domain. Hashing a
779
+ * password, deriving a slug, writing an audit row, sending a notification -
780
+ * none of these can be inferred from a column type, and all of them are the
781
+ * reason a real application eventually stops using a generic admin. This is the
782
+ * seam where they go in.
783
+ *
784
+ * ## Where they run
785
+ *
786
+ * After authorization and after validation, immediately around the adapter
787
+ * call. A hook is therefore never reached for a request that would have been
788
+ * refused, and never sees a payload naming a hidden or read-only field.
789
+ *
790
+ * ## What a `before` hook returns
791
+ *
792
+ * The data to write. Returning a changed object is how a value is added or
793
+ * rewritten, and returning the object unchanged is fine. It is not applied
794
+ * blindly: the result is validated again, so a hook cannot introduce a field
795
+ * the admin refuses to write.
796
+ *
797
+ * ## Failing
798
+ *
799
+ * Throw. A `ValidationError` reaches the caller with its message intact and is
800
+ * the way to refuse an input for a reason a person should read. Anything else
801
+ * becomes a 500 with the message withheld, which is the right treatment for a
802
+ * hook that broke rather than one that objected.
803
+ *
804
+ * Nothing is transactional. An `after` hook that throws leaves the write
805
+ * already done, so it should be used for work whose failure is not worse than
806
+ * its absence - and anything that must be atomic belongs in the application's
807
+ * own transaction, not here.
808
+ */
809
+
810
+ /** What every hook is given. */
811
+ interface AdminHookContext {
812
+ /**
813
+ * The NestJS execution context for the request being served.
814
+ *
815
+ * Reach the principal through it, exactly as in `AdminAuth.authorize` and
816
+ * `AdminResourceAuth.authorize` - one accessor works for all three.
817
+ */
818
+ readonly context: ExecutionContext;
819
+ /** The model being written. */
820
+ readonly model: string;
821
+ }
822
+ interface AdminHooks {
823
+ /** Runs before a record is created. Returns the data to write. */
824
+ readonly beforeCreate?: (args: AdminHookContext & {
825
+ readonly data: RecordData;
826
+ }) => RecordData | Promise<RecordData>;
827
+ /** Runs after a record is created. Its return value is ignored. */
828
+ readonly afterCreate?: (args: AdminHookContext & {
829
+ readonly record: RecordData;
830
+ }) => void | Promise<void>;
831
+ /**
832
+ * Runs before a record is updated. Returns the data to write.
833
+ *
834
+ * The data is the *patch*, not the whole record: only the fields the request
835
+ * named are present.
836
+ */
837
+ readonly beforeUpdate?: (args: AdminHookContext & {
838
+ readonly id: RecordId;
839
+ readonly data: RecordData;
840
+ }) => RecordData | Promise<RecordData>;
841
+ readonly afterUpdate?: (args: AdminHookContext & {
842
+ readonly id: RecordId;
843
+ readonly record: RecordData;
844
+ }) => void | Promise<void>;
845
+ /**
846
+ * Runs before a record is deleted.
847
+ *
848
+ * Throw to refuse the deletion - a `ValidationError` says why in a way the
849
+ * caller can read.
850
+ */
851
+ readonly beforeDelete?: (args: AdminHookContext & {
852
+ readonly id: RecordId;
853
+ }) => void | Promise<void>;
854
+ /** Runs after a record is deleted. The record is already gone. */
855
+ readonly afterDelete?: (args: AdminHookContext & {
856
+ readonly id: RecordId;
857
+ }) => void | Promise<void>;
858
+ }
859
+ /** Hooks per model. Models without an entry have none. */
860
+ type AdminHooksByModel = Readonly<Record<string, AdminHooks>>;
861
+
862
+ /**
863
+ * Implemented by the consuming application and passed to
864
+ * `AdminModule.forRoot({ auth })`.
865
+ *
866
+ * ```ts
867
+ * const auth: AdminAuth = {
868
+ * authorize(context) {
869
+ * const request = context.switchToHttp().getRequest()
870
+ * if (!request.user) throw new UnauthorizedError()
871
+ * if (!request.user.isStaff) throw new ForbiddenError()
872
+ * },
873
+ * }
874
+ * ```
875
+ */
876
+ interface AdminAuth {
877
+ /**
878
+ * Decide whether the request may proceed.
879
+ *
880
+ * Return (or resolve) normally to allow it. To deny it, throw:
881
+ *
882
+ * - {@link UnauthorizedError} - no identity was established. 401.
883
+ * - {@link ForbiddenError} - an identity exists but may not do this. 403.
884
+ *
885
+ * Throwing is the intended way to deny, because it forces the caller to say
886
+ * *which* denial it is. A client cannot act on "denied"; it can act on "log
887
+ * in" versus "you may not do this".
888
+ *
889
+ * Returning `false` is also treated as a denial, mapped to `403`, so a guard
890
+ * written in the reflexive NestJS style still fails closed rather than
891
+ * silently allowing the request. Prefer throwing: `false` cannot express the
892
+ * 401/403 distinction, and 403 is only the safer of the two guesses.
893
+ *
894
+ * May be synchronous or asynchronous.
895
+ *
896
+ * @param context The NestJS execution context. Use it to reach the request -
897
+ * including any principal the host application already attached, and the
898
+ * `model` route parameter on per-model routes.
899
+ */
900
+ authorize(context: ExecutionContext): void | boolean | Promise<void | boolean>;
901
+ }
902
+ /**
903
+ * An {@link AdminAuth} that permits every request. **The admin API becomes
904
+ * completely public.**
905
+ *
906
+ * It exists because `auth` is required, and a required option with no escape
907
+ * hatch pushes people toward writing their own always-allow implementation -
908
+ * which is the same hole, only invisible in review. This one is deliberately
909
+ * hard to mistake for anything else: the name says `unsafe`, and it logs a
910
+ * warning every time an application starts with it.
911
+ *
912
+ * Intended for local development, examples and tests. Never for a deployed
913
+ * application.
914
+ */
915
+ declare function unsafeAllowAllRequests(): AdminAuth;
916
+
917
+ /**
918
+ * The resource authorization boundary.
919
+ *
920
+ * Phase 4 gave the host a say over whether a *request* may enter the admin at
921
+ * all (`AdminAuth`). This answers a narrower question: may this principal touch
922
+ * *this model*, for *this operation*?
923
+ *
924
+ * The two are deliberately separate contracts. A host that only needs "staff
925
+ * only" implements `AdminAuth` and stops; a host that needs "support can read
926
+ * Users but nobody outside finance sees Payment" adds this one. Folding them
927
+ * together would force every consumer to think about resources whether or not
928
+ * they have per-resource rules.
929
+ *
930
+ * Why this exists at all: a host can already deny per model from `AdminAuth`,
931
+ * because the guard sees `params.model`. But `GET /admin/meta` has no `:model`
932
+ * segment, so route-level checks cannot stop the metadata endpoint from
933
+ * describing every table in the database - and the admin UI renders itself from
934
+ * that endpoint. Resource authorization has to live where metadata is produced.
935
+ */
936
+
937
+ /**
938
+ * What the caller is trying to do.
939
+ *
940
+ * `'metadata'` is the odd one out: it is not an operation on records, it asks
941
+ * whether the model should be *visible* to this principal at all. A model that
942
+ * fails a `'metadata'` check disappears from `GET /admin/meta` entirely.
943
+ */
944
+ type AdminOperation = 'metadata' | 'list' | 'read' | 'create' | 'update' | 'delete'
945
+ /**
946
+ * An application-defined action.
947
+ *
948
+ * Distinct from `update` because an action can do anything, including things
949
+ * no CRUD route offers, so a policy should be able to decide about it
950
+ * separately. A policy written before actions existed does not recognise the
951
+ * value and denies it, which is the right direction to fail in.
952
+ */
953
+ | 'action';
954
+ /** Everything the policy is given to decide with. */
955
+ interface ResourceAuthorization {
956
+ /**
957
+ * The NestJS execution context for the request being served. Use it to reach
958
+ * whatever principal the host application attached to the request - exactly
959
+ * as in `AdminAuth.authorize`, so one accessor works for both contracts.
960
+ */
961
+ readonly context: ExecutionContext;
962
+ /** The model name as the schema declares it, e.g. `User`. */
963
+ readonly model: string;
964
+ readonly operation: AdminOperation;
965
+ }
966
+ /**
967
+ * Implemented by the consuming application and passed to
968
+ * `AdminModule.forRoot({ resourceAuth })`.
969
+ *
970
+ * ```ts
971
+ * const resourceAuth: AdminResourceAuth = {
972
+ * authorize({ context, model, operation }) {
973
+ * const { user } = context.switchToHttp().getRequest()
974
+ * if (model === 'AuditLog') return user.isAdmin
975
+ * if (operation === 'delete') return user.isAdmin
976
+ * return true
977
+ * },
978
+ * }
979
+ * ```
980
+ */
981
+ interface AdminResourceAuth {
982
+ /**
983
+ * Decide whether this principal may perform `operation` on `model`.
984
+ *
985
+ * Return `true`, or return nothing, to allow. To deny, return `false` or
986
+ * throw `ForbiddenError`. Both are treated identically - unlike
987
+ * `AdminAuth`, there is no 401/403 ambiguity to resolve here, because a
988
+ * request that reached this point has already passed authentication.
989
+ *
990
+ * The consequence of a denial depends on the operation:
991
+ *
992
+ * - `'metadata'` - the model is **omitted** from `GET /admin/meta`. It is not
993
+ * an error; the response simply describes a smaller schema.
994
+ * - everything else - the request fails with `403 FORBIDDEN`, and the ORM
995
+ * adapter is never called.
996
+ *
997
+ * Anything else thrown is treated as a bug in the host's policy: the request
998
+ * fails with a generic 500 and the real error is logged. A failing policy
999
+ * never allows access.
1000
+ *
1001
+ * May be synchronous or asynchronous.
1002
+ */
1003
+ authorize(resource: ResourceAuthorization): void | boolean | Promise<void | boolean>;
1004
+ }
1005
+
1006
+ /**
1007
+ * What an application puts on the dashboard.
1008
+ *
1009
+ * ## A closed set of four
1010
+ *
1011
+ * `count`, `list`, `chart`, `stat`. Closed for the same reason `FieldWidget` is:
1012
+ * the interface has to know how to draw each one, so an open string would mean
1013
+ * rendering nothing and no way to notice.
1014
+ *
1015
+ * It is also the line this release does not cross. An arbitrary React component
1016
+ * would mean the consuming application builds and bundles one, which is exactly
1017
+ * the thing this package exists not to make people do - and the reason custom
1018
+ * pages have been out of scope since 0.6.0.
1019
+ *
1020
+ * ## Three of them are declarative on purpose
1021
+ *
1022
+ * `count`, `list` and `chart` name a model and a filter; the server does the
1023
+ * work. That is not just terseness. A widget that names a model can be
1024
+ * *authorized*: one over a resource this principal cannot see is absent from
1025
+ * the document, the same way a hidden model and a refused action already are.
1026
+ * A widget built from a closure could not be checked, only trusted.
1027
+ *
1028
+ * `stat` is the escape hatch and has no model, because the number it shows may
1029
+ * come from anywhere - a payment processor, a queue, three tables joined. It
1030
+ * runs application code, so the application's own rules apply to it.
1031
+ *
1032
+ * ## Nothing is configured by default
1033
+ *
1034
+ * An admin with no `dashboard` option still gets one, built from metadata
1035
+ * alone: a count per model, and recent records where the schema says when a
1036
+ * record was created. Declaring widgets replaces that rather than adding to it,
1037
+ * because a dashboard is a page someone designed, and half-designed is worse
1038
+ * than either.
1039
+ */
1040
+
1041
+ /** How wide a widget sits in the four-column grid. */
1042
+ type WidgetSpan = 1 | 2 | 3 | 4;
1043
+ interface Common {
1044
+ /** Shown above it. The one thing every widget needs. */
1045
+ readonly title: string;
1046
+ /** A sentence under the title, when the title cannot carry it alone. */
1047
+ readonly description?: string;
1048
+ /** Columns out of four. Sensible per kind when omitted. */
1049
+ readonly span?: WidgetSpan;
1050
+ }
1051
+ /**
1052
+ * A single number, from a model.
1053
+ *
1054
+ * The most common thing on any dashboard, and the reason it is declarative:
1055
+ * "how many open orders" is a model, a filter, and nothing else.
1056
+ */
1057
+ interface CountWidget extends Common {
1058
+ readonly kind: 'count';
1059
+ readonly model: string;
1060
+ /** `field:op:value`, the same syntax the list screen's URL uses. */
1061
+ readonly filter?: string;
1062
+ /**
1063
+ * Compare against the same count a period ago, and show the change.
1064
+ *
1065
+ * Needs the model to have a creation timestamp; the comparison is silently
1066
+ * omitted when it does not, rather than the widget disappearing.
1067
+ */
1068
+ readonly compareDays?: number;
1069
+ }
1070
+ /** A few records, most recent first where the model says which those are. */
1071
+ interface ListWidget extends Common {
1072
+ readonly kind: 'list';
1073
+ readonly model: string;
1074
+ readonly filter?: string;
1075
+ /** How many rows. Five by default; more than ten belongs on the list screen. */
1076
+ readonly limit?: number;
1077
+ }
1078
+ /** How many records appeared per day, week or month. */
1079
+ interface ChartWidget extends Common {
1080
+ readonly kind: 'chart';
1081
+ readonly model: string;
1082
+ readonly filter?: string;
1083
+ readonly bucket?: 'day' | 'week' | 'month';
1084
+ /** How many buckets. Thirty by default, ninety at most - see the service. */
1085
+ readonly buckets?: number;
1086
+ }
1087
+ /**
1088
+ * A number the application works out for itself.
1089
+ *
1090
+ * The escape hatch, and the only widget that runs application code. Whatever it
1091
+ * returns is shown; whatever it throws becomes a widget that says it could not
1092
+ * load, rather than a dashboard that does not.
1093
+ */
1094
+ interface StatWidget extends Common {
1095
+ readonly kind: 'stat';
1096
+ readonly load: (args: {
1097
+ readonly context: ExecutionContext;
1098
+ }) => Promise<StatResult> | StatResult;
1099
+ }
1100
+ interface StatResult {
1101
+ /** Shown large. A string is passed through, so it can carry a currency. */
1102
+ readonly value: string | number;
1103
+ /** A change against some previous period, as a percentage. */
1104
+ readonly delta?: number;
1105
+ /** Under the value. "vs last month", "across 4 regions". */
1106
+ readonly hint?: string;
1107
+ }
1108
+ type DashboardWidget = CountWidget | ListWidget | ChartWidget | StatWidget;
1109
+ /**
1110
+ * The dashboard an application declares.
1111
+ *
1112
+ * An array rather than a keyed object: a dashboard is read top to bottom, and
1113
+ * the order things appear in is part of the design.
1114
+ */
1115
+ type AdminDashboard = readonly DashboardWidget[];
1116
+
1117
+ interface AdminTheme {
1118
+ /**
1119
+ * Accent colour, as a CSS hex value - `#0b6e6e` or `#0b6`.
1120
+ *
1121
+ * Hex only. A full CSS colour grammar would mean parsing one, and a value
1122
+ * this small is not worth a parser; named colours and `rgb()` are excluded
1123
+ * for the same reason.
1124
+ */
1125
+ readonly brandColor?: string;
1126
+ /** Page title and the name shown in the header. Plain text. */
1127
+ readonly title?: string;
1128
+ /**
1129
+ * Logo shown beside the title.
1130
+ *
1131
+ * An `http(s)` URL or a `data:image/...` URI. Other schemes are refused:
1132
+ * `javascript:` in an image source is the obvious one, but the rule is a
1133
+ * whitelist rather than a blacklist so there is nothing to keep up with.
1134
+ */
1135
+ readonly logoUrl?: string;
1136
+ /**
1137
+ * Which appearance to start from, before anyone chooses.
1138
+ *
1139
+ * `'system'` follows the viewer's operating system and is the default. A
1140
+ * viewer's own choice, once made, wins over this and is remembered by their
1141
+ * browser - so this sets the first impression rather than a policy.
1142
+ */
1143
+ readonly appearance?: 'system' | 'light' | 'dark';
1144
+ }
1145
+
1146
+ /**
1147
+ * `AdminModule` - the NestJS integration.
1148
+ *
1149
+ * ```ts
1150
+ * AdminModule.forRoot({
1151
+ * adapter: new PrismaAdapter({ client: prisma }),
1152
+ * auth: myAdminAuth,
1153
+ * })
1154
+ * ```
1155
+ *
1156
+ * The module wires an `OrmAdapter` and an `AdminAuth` into the admin HTTP
1157
+ * layer and does nothing else. It does not construct a database client, does
1158
+ * not authenticate anyone, does not read configuration from disk, and holds no
1159
+ * module-level mutable state - so two instances in the same process cannot
1160
+ * interfere with each other.
1161
+ *
1162
+ * It is not `@Global()`: making a library's providers globally visible in
1163
+ * someone else's application is a decision the application should make.
1164
+ */
1165
+
1166
+ interface AdminModuleOptions {
1167
+ /**
1168
+ * The ORM adapter the admin reads and writes through.
1169
+ *
1170
+ * Constructed by the consuming application, never by the framework: under
1171
+ * Prisma 7 a client is built from a driver adapter, so only the application
1172
+ * knows the provider, the credentials and the connection strategy.
1173
+ */
1174
+ readonly adapter: OrmAdapter;
1175
+ /**
1176
+ * Decides whether a request may reach the admin.
1177
+ *
1178
+ * **Required, deliberately.** The admin exposes every record in the database
1179
+ * and, through `/admin/meta`, the shape of the entire schema. An optional
1180
+ * option defaulting to "open" would mean a forgotten line in a config file
1181
+ * silently publishes the database - the failure would be invisible until
1182
+ * someone else found it.
1183
+ *
1184
+ * For local development and examples, pass `unsafeAllowAllRequests()`, which
1185
+ * is explicit at the call site and warns at startup.
1186
+ */
1187
+ readonly auth: AdminAuth;
1188
+ /**
1189
+ * Decides which models this principal may see and act on.
1190
+ *
1191
+ * Optional, defaulting to allowing every model. Unlike `auth`, that default
1192
+ * is not a hole: `auth` is required, so the door is already shut, and
1193
+ * omitting this only means everyone admitted sees the whole schema - exactly
1194
+ * the behaviour before the option existed. Requiring it would break every
1195
+ * existing consumer to express a rule most applications do not have.
1196
+ *
1197
+ * Supply it when some models should be invisible or read-only to some
1198
+ * principals. A model denied for `'metadata'` disappears from
1199
+ * `GET /admin/meta`; a model denied for any other operation makes the request
1200
+ * fail with 403 before the ORM adapter is called.
1201
+ */
1202
+ readonly resourceAuth?: AdminResourceAuth;
1203
+ /**
1204
+ * Where the admin is mounted. Defaults to `/admin`.
1205
+ *
1206
+ * Accepts `admin`, `/admin` and `/admin/` alike, and may be nested, as in
1207
+ * `/internal/admin`. It cannot be empty or `/`: these routes end in
1208
+ * `:model`, so mounting them at the root would capture every unmatched
1209
+ * request in the host application.
1210
+ *
1211
+ * The API and the UI move together. There is one mount point, not two.
1212
+ */
1213
+ readonly path?: string;
1214
+ /**
1215
+ * Which models the admin exposes at all. Defaults to every model the adapter
1216
+ * reports.
1217
+ *
1218
+ * Structural, and not a substitute for `resourceAuth`: this is the same for
1219
+ * every principal, so an excluded model answers 404 rather than 403. Use it
1220
+ * for tables that are not domain data - session stores, migration
1221
+ * bookkeeping, queues - and `resourceAuth` for who may do what.
1222
+ *
1223
+ * A name that matches no model fails at startup rather than being ignored: a
1224
+ * typo in `exclude` would otherwise leave the model exposed.
1225
+ */
1226
+ readonly resources?: ResourceSelection;
1227
+ /**
1228
+ * Per-model configuration: labels, widgets, ordering, and the two that are
1229
+ * enforced rather than suggested - hidden and readOnly.
1230
+ *
1231
+ * A hidden field is removed from the metadata every layer reads, so it cannot
1232
+ * be filtered, sorted, written, or returned. A name matching no model or
1233
+ * field fails at startup.
1234
+ */
1235
+ readonly models?: ModelOverrides;
1236
+ /**
1237
+ * Application code that runs around a write, per model.
1238
+ *
1239
+ * Where hashing a password, deriving a slug or writing an audit row goes -
1240
+ * none of which can be inferred from a column type. See `AdminHooks`.
1241
+ */
1242
+ readonly hooks?: AdminHooksByModel;
1243
+ /**
1244
+ * Buttons the application adds, per model.
1245
+ *
1246
+ * CRUD covers what a schema implies; "publish" and "resend the invitation"
1247
+ * are obvious to the domain and invisible to the database. See `AdminAction`.
1248
+ */
1249
+ readonly actions?: AdminActionsByModel;
1250
+ /**
1251
+ * Branding the served page applies without a rebuild: an accent colour, a
1252
+ * title, a logo. Structural, because the page is rendered before any
1253
+ * provider exists.
1254
+ */
1255
+ readonly theme?: AdminTheme;
1256
+ /**
1257
+ * What the dashboard shows.
1258
+ *
1259
+ * Omit it and the dashboard is built from the schema: a count per model, the
1260
+ * newest records, and a month of activity. Declaring widgets replaces that
1261
+ * rather than adding to it - a dashboard is a page someone designed, and
1262
+ * half-designed is worse than either.
1263
+ */
1264
+ readonly dashboard?: AdminDashboard;
1265
+ /**
1266
+ * Directory holding the built admin UI.
1267
+ *
1268
+ * Defaults to the copy bundled inside this package, which is what a consumer
1269
+ * wants and why it is optional. Overriding it exists for this repository's
1270
+ * own tests, which run from `src` while the built UI lives in `dist`.
1271
+ *
1272
+ * @internal
1273
+ */
1274
+ readonly uiRoot?: string;
1275
+ }
1276
+ /**
1277
+ * What a `forRootAsync` factory returns.
1278
+ *
1279
+ * Everything except the structural options, which are decided when the module
1280
+ * is defined and so cannot come from a provider - see `forRootAsync`.
1281
+ */
1282
+ type AdminModuleFactoryOptions = Omit<AdminModuleOptions, 'path' | 'uiRoot' | 'theme'>;
1283
+ /** Supply options from a class rather than a factory function. */
1284
+ interface AdminModuleOptionsFactory {
1285
+ createAdminOptions(): AdminModuleFactoryOptions | Promise<AdminModuleFactoryOptions>;
1286
+ }
1287
+ interface AdminModuleAsyncOptions {
1288
+ /** As `AdminModuleOptions.path`. Structural, so it is not from the factory. */
1289
+ readonly path?: string;
1290
+ /** @internal As `AdminModuleOptions.uiRoot`. */
1291
+ readonly uiRoot?: string;
1292
+ /**
1293
+ * As `AdminModuleOptions.theme`. Structural, so it is not from the factory:
1294
+ * the shell is rendered from it and no provider exists at that point.
1295
+ */
1296
+ readonly theme?: AdminTheme;
1297
+ /** Modules whose providers the factory needs. */
1298
+ readonly imports?: ModuleMetadata['imports'];
1299
+ /** Providers passed to `useFactory`, in order. */
1300
+ readonly inject?: FactoryProvider['inject'];
1301
+ readonly useFactory?: (...args: never[]) => AdminModuleFactoryOptions | Promise<AdminModuleFactoryOptions>;
1302
+ /** Instantiated by Nest, then asked for the options. */
1303
+ readonly useClass?: Type<AdminModuleOptionsFactory>;
1304
+ /** An options factory the application already provides elsewhere. */
1305
+ readonly useExisting?: Type<AdminModuleOptionsFactory>;
1306
+ }
1307
+ declare class AdminModule {
1308
+ static forRoot(options: AdminModuleOptions): DynamicModule;
1309
+ /**
1310
+ * The same module, with the adapter and the auth policy resolved through DI.
1311
+ *
1312
+ * For the ordinary case where those things are not available when the module
1313
+ * is declared: a `PrismaService` that belongs to another module, a connection
1314
+ * string that comes from `ConfigService`.
1315
+ *
1316
+ * ```ts
1317
+ * AdminModule.forRootAsync({
1318
+ * imports: [PrismaModule, ConfigModule],
1319
+ * inject: [PrismaService, ConfigService],
1320
+ * useFactory: (prisma: PrismaService, config: ConfigService) => ({
1321
+ * adapter: new PrismaAdapter({ client: prisma }),
1322
+ * auth: new SessionAdminAuth(config.get('ADMIN_ROLE')),
1323
+ * }),
1324
+ * })
1325
+ * ```
1326
+ *
1327
+ * `path` stays on this object rather than coming from the factory. Routes are
1328
+ * registered when the module is defined, which is before any provider has
1329
+ * been instantiated, so the mount path cannot wait for an injection - and a
1330
+ * `path` returned from the factory would be silently ignored, which is worse
1331
+ * than not offering it.
1332
+ */
1333
+ static forRootAsync(options: AdminModuleAsyncOptions): DynamicModule;
1334
+ }
1335
+
1336
+ /**
1337
+ * An `AdminAuth` that ships in the box.
1338
+ *
1339
+ * ## This does not move the boundary
1340
+ *
1341
+ * `AdminAuth` is unchanged and still the only way in. An application with its
1342
+ * own identity system implements it and never sees any of this. What changes is
1343
+ * that an application *without* one no longer has to write a password hash, a
1344
+ * cookie and a form before the admin can be put behind a login.
1345
+ *
1346
+ * So there are three answers to "who may open this?", and a consumer picks one:
1347
+ *
1348
+ * auth: unsafeAllowAllRequests() development only, warns at startup
1349
+ * auth: myOwnAuth an application that already has identity
1350
+ * auth: builtInAuth({ ... }) a login page, sessions and a store
1351
+ *
1352
+ * ## The accounts are separate from the application's users
1353
+ *
1354
+ * By construction: the store is a contract over storage the application
1355
+ * nominates, and the intended shape is a model of its own. The admin never
1356
+ * consults the application's user table to decide who may sign in, and adding
1357
+ * a customer never adds someone who can administer the system.
1358
+ */
1359
+
1360
+ interface BuiltInAuthOptions {
1361
+ /**
1362
+ * Where the accounts live.
1363
+ *
1364
+ * `prismaAccountStore` from `@nest-admin/nestjs/prisma` covers the usual
1365
+ * case; anything satisfying the contract works.
1366
+ */
1367
+ readonly store: AdminAccountStore;
1368
+ readonly session: {
1369
+ /**
1370
+ * The key the session cookie is signed with. **Required.**
1371
+ *
1372
+ * At least 32 characters, checked at startup. A short secret is a
1373
+ * forgeable cookie, and the failure is silent: everything works, and
1374
+ * anybody can mint a session for any account.
1375
+ *
1376
+ * Read it from the environment. A secret in source control is a secret
1377
+ * everyone who has ever cloned the repository knows.
1378
+ */
1379
+ readonly secret: string;
1380
+ /** How long a session lasts, in seconds. Twelve hours by default. */
1381
+ readonly maxAge?: number;
1382
+ /** The cookie's name. Change it only to avoid a collision. */
1383
+ readonly cookieName?: string;
1384
+ /**
1385
+ * Send the cookie only over HTTPS.
1386
+ *
1387
+ * Left unset it is decided per request: on for everything except
1388
+ * localhost, which is what makes the admin work in development without
1389
+ * being insecure anywhere else. Set it to `true` to require HTTPS always.
1390
+ */
1391
+ readonly secure?: boolean;
1392
+ };
1393
+ /** Failed attempts before a pause. Ten by default. */
1394
+ readonly maxAttempts?: number;
1395
+ /** How long that pause lasts, in seconds. Fifteen minutes by default. */
1396
+ readonly lockoutSeconds?: number;
1397
+ }
1398
+ /**
1399
+ * The account this request signed in as.
1400
+ *
1401
+ * For a `resourceAuth` policy or a hook that needs to know who is asking.
1402
+ * `undefined` when the admin is not using the built-in auth, which is why it
1403
+ * is optional rather than assumed.
1404
+ */
1405
+ declare function adminAccountOf(context: ExecutionContext): AdminAccountSummary | undefined;
1406
+ declare function builtInAuth(options: BuiltInAuthOptions): AdminAuth;
1407
+
1408
+ /**
1409
+ * Hash a password for storage.
1410
+ *
1411
+ * Exported for the application, because creating accounts is its business -
1412
+ * a seed script, a migration, or a form of its own. The admin never mints an
1413
+ * administrator; see `AdminAccountStore` for why.
1414
+ */
1415
+ declare function hashAdminPassword(password: string): Promise<string>;
1416
+ /**
1417
+ * Does this password match this stored hash?
1418
+ *
1419
+ * Never throws for a malformed or unrecognised hash - it answers `false`. A
1420
+ * store holding something this function does not understand is a configuration
1421
+ * problem, and turning it into a 500 on the login route would tell an attacker
1422
+ * that the account exists and that its record is unusual.
1423
+ *
1424
+ * The comparison is `timingSafeEqual`, not `===`. String equality returns as
1425
+ * soon as two bytes differ, and the difference is measurable often enough to
1426
+ * recover a hash a byte at a time.
1427
+ */
1428
+ declare function verifyAdminPassword(password: string, stored: string): Promise<boolean>;
1429
+
1430
+ /** A secret of the right shape, for a consumer that needs one generated. */
1431
+ declare function generateSessionSecret(): string;
1432
+
1433
+ /**
1434
+ * The response envelope.
1435
+ *
1436
+ * Every admin endpoint returns the same two shapes, so a generic frontend can
1437
+ * branch on one field rather than on status codes plus per-endpoint knowledge.
1438
+ *
1439
+ * Success: { success: true, data: <payload>, meta?: <pagination> }
1440
+ * Failure: { success: false, error: { code, message, details? } }
1441
+ *
1442
+ * @experimental The HTTP contract is expected to change before 1.0.
1443
+ */
1444
+ /** Pagination facts a list response carries alongside its rows. */
1445
+ interface PageMeta {
1446
+ readonly total: number;
1447
+ readonly page: number;
1448
+ readonly perPage: number;
1449
+ }
1450
+ interface SuccessResponse<T> {
1451
+ readonly success: true;
1452
+ readonly data: T;
1453
+ readonly meta?: PageMeta;
1454
+ }
1455
+ /**
1456
+ * Stable, machine-readable error codes.
1457
+ *
1458
+ * Clients branch on these, never on the human-readable message. Adding a code
1459
+ * is a compatible change; renaming one is not.
1460
+ */
1461
+ type AdminErrorCode = 'UNAUTHORIZED' | 'FORBIDDEN' | 'MODEL_NOT_FOUND' | 'RECORD_NOT_FOUND' | 'FIELD_NOT_FOUND' | 'INVALID_QUERY' | 'VALIDATION_ERROR' | 'CONSTRAINT_VIOLATION' | 'INTERNAL_ERROR';
1462
+ interface ErrorResponse {
1463
+ readonly success: false;
1464
+ readonly error: {
1465
+ readonly code: AdminErrorCode;
1466
+ readonly message: string;
1467
+ /** Structured context, e.g. `{ model, field }`. Never internal detail. */
1468
+ readonly details?: Readonly<Record<string, unknown>>;
1469
+ };
1470
+ }
1471
+ type AdminResponse<T> = SuccessResponse<T> | ErrorResponse;
1472
+
1473
+ /**
1474
+ * The public HTTP representation of model metadata.
1475
+ *
1476
+ * This is the contract between the backend and any future admin frontend, and
1477
+ * it is deliberately declared separately from Core's `ModelMetadata` rather
1478
+ * than serialised straight from it. Two reasons:
1479
+ *
1480
+ * 1. Core's contract is marked `@experimental` and will keep moving. The wire
1481
+ * format must not move with it by accident.
1482
+ * 2. An explicit mapper is a whitelist. If a future adapter puts something
1483
+ * ORM-specific on `FieldMetadata`, it cannot silently reach a client.
1484
+ *
1485
+ * Nothing here mentions Prisma, DMMF, or any ORM. Replacing the Prisma adapter
1486
+ * with another one must not change a single byte of this shape.
1487
+ *
1488
+ * @experimental The HTTP contract is expected to change before 1.0.
1489
+ */
1490
+
1491
+ /** Mirrors Core's `FieldKind`, restated so the wire format is self-contained. */
1492
+ type FieldKindDto = 'string' | 'number' | 'boolean' | 'datetime' | 'enum' | 'json' | 'relation' | 'unknown';
1493
+ interface RelationDto {
1494
+ readonly targetModel: string;
1495
+ readonly cardinality: 'one' | 'many';
1496
+ /**
1497
+ * Scalar field on this model holding the key, for a to-one relation.
1498
+ *
1499
+ * The UI needs it twice over: it is the field a form submits when the user
1500
+ * picks a related record, and the field a filter is expressed in. Absent on
1501
+ * to-many relations, which have no column on this side.
1502
+ */
1503
+ readonly from?: string;
1504
+ /** Field on the target the key points at - usually its id. */
1505
+ readonly to?: string;
1506
+ /**
1507
+ * Where the link is stored, which decides what may be done to it.
1508
+ *
1509
+ * Computed on the server rather than left for the client to derive, for the
1510
+ * same reason as `displayField`: working it out needs the other half of the
1511
+ * relation, and two implementations of that rule would drift. A client that
1512
+ * guessed wrong would offer a button that cannot work.
1513
+ */
1514
+ readonly shape?: 'to-one' | 'one-to-many' | 'many-to-many';
1515
+ /**
1516
+ * Why records cannot be detached from this relation, when they cannot.
1517
+ *
1518
+ * Present only for a one-to-many whose child key is required: such a child
1519
+ * cannot exist without a parent, so there is nothing to detach it to.
1520
+ */
1521
+ readonly detachBlocked?: string;
1522
+ /**
1523
+ * The column on the target model that points back at this one.
1524
+ *
1525
+ * What "all the posts by this author" is expressed as:
1526
+ * `?filter=<targetForeignKey>:eq:<parentId>`. Present only for a one-to-many,
1527
+ * since a many-to-many has no such column on either side.
1528
+ *
1529
+ * Sent rather than derived, for the same reason as `shape`: finding it means
1530
+ * pairing the two halves of the relation, and a rule implemented twice is a
1531
+ * rule that will eventually disagree with itself.
1532
+ */
1533
+ readonly targetForeignKey?: string;
1534
+ }
1535
+ interface FieldDto {
1536
+ readonly name: string;
1537
+ readonly kind: FieldKindDto;
1538
+ /** Part of the model's primary key. */
1539
+ readonly isId: boolean;
1540
+ readonly isRequired: boolean;
1541
+ readonly isUnique: boolean;
1542
+ readonly isList: boolean;
1543
+ /**
1544
+ * Produced by the database or ORM (`cuid()`, `now()`, `autoincrement()`,
1545
+ * `@updatedAt`). Display it; do not ask the user for it.
1546
+ */
1547
+ readonly isGenerated: boolean;
1548
+ /**
1549
+ * Literal default to pre-fill on create. Present only for editable fields
1550
+ * that declare one - a generated value has no literal to pre-fill.
1551
+ */
1552
+ readonly defaultValue?: unknown;
1553
+ /** Present when `kind` is `'enum'`. */
1554
+ readonly enumValues?: readonly string[];
1555
+ /**
1556
+ * What to call the field, when the column name is not what people call it.
1557
+ *
1558
+ * Absent unless the application said so. A client falls back to `name`.
1559
+ */
1560
+ readonly label?: string;
1561
+ /**
1562
+ * How the field should be edited, when its kind does not say enough.
1563
+ *
1564
+ * A `string` column may be a sentence, a password or a colour, and the schema
1565
+ * cannot tell them apart.
1566
+ */
1567
+ readonly widget?: 'textarea' | 'password' | 'email' | 'url' | 'color' | 'json';
1568
+ /**
1569
+ * The admin will refuse to write this field.
1570
+ *
1571
+ * True for generated columns, and for anything the application marked
1572
+ * read-only. Enforced: a write naming it is rejected, so a client that
1573
+ * ignores this gets a 400 rather than a surprise.
1574
+ */
1575
+ readonly readOnly: boolean;
1576
+ /**
1577
+ * The admin accepts this field on a write and never sends it back.
1578
+ *
1579
+ * Sent so the interface knows the blank it shows is not the stored value. A
1580
+ * password field that looked empty because the record had none would be a
1581
+ * different thing entirely.
1582
+ */
1583
+ readonly writeOnly?: boolean;
1584
+ /** Present when `kind` is `relation`. */
1585
+ readonly relation?: RelationDto;
1586
+ }
1587
+ /** Which operations a principal may perform on one model. */
1588
+ interface ModelPermissionsDto {
1589
+ readonly list: boolean;
1590
+ readonly read: boolean;
1591
+ readonly create: boolean;
1592
+ readonly update: boolean;
1593
+ readonly delete: boolean;
1594
+ }
1595
+ /** An application-defined button the interface should draw. */
1596
+ interface ActionDto {
1597
+ readonly name: string;
1598
+ readonly label: string;
1599
+ readonly scope: 'record' | 'list';
1600
+ /** Ask this before running. Absent means run straight away. */
1601
+ readonly confirm?: string;
1602
+ /** Draw it as destructive. */
1603
+ readonly danger?: boolean;
1604
+ }
1605
+ interface ModelDto {
1606
+ readonly name: string;
1607
+ /** Field names forming the primary key. Single-column in this version. */
1608
+ readonly primaryKey: readonly string[];
1609
+ readonly fields: readonly FieldDto[];
1610
+ /**
1611
+ * Field that names a record of this model in one line.
1612
+ *
1613
+ * Sent rather than left for the UI to guess, because the guess would have to
1614
+ * match what the adapter already selected when it loaded the relation. Both
1615
+ * come from one rule in Core, so they cannot disagree.
1616
+ */
1617
+ readonly displayField: string;
1618
+ /**
1619
+ * What this principal may do with the model.
1620
+ *
1621
+ * Sent so the interface can stop offering actions that will be refused. It is
1622
+ * a description of the policy's answers, not the enforcement: every request is
1623
+ * checked again when it arrives, and a client that ignores this gets a 403
1624
+ * rather than access.
1625
+ *
1626
+ * `metadata` is not among them - a model the principal cannot see is absent
1627
+ * from this document entirely.
1628
+ */
1629
+ readonly can: ModelPermissionsDto;
1630
+ /**
1631
+ * Application-defined actions this principal may run.
1632
+ *
1633
+ * Already filtered by the policy: an action that would be refused is absent,
1634
+ * so the interface never draws a button that cannot work.
1635
+ */
1636
+ readonly actions: readonly ActionDto[];
1637
+ /** What to call the model. Absent unless the application said so. */
1638
+ readonly label?: string;
1639
+ /**
1640
+ * Which icon to draw beside it in the navigation.
1641
+ *
1642
+ * One of a closed set the interface knows how to render - see `ModelIcon` in
1643
+ * Core. Absent unless the application named one, and absent is a real answer:
1644
+ * the same icon repeated down a column is decoration.
1645
+ */
1646
+ readonly icon?: ModelIcon;
1647
+ }
1648
+ interface MetadataDto {
1649
+ readonly models: readonly ModelDto[];
1650
+ }
1651
+
1652
+ export { type ActionDto, AdapterError, type AdminAccount, type AdminAccountStore, type AdminAccountSummary, type AdminAction, type AdminActionResult, type AdminActionsByModel, type AdminAuth, type AdminDashboard, type AdminErrorCode, type AdminErrorKind, type AdminHookContext, type AdminHooks, type AdminHooksByModel, AdminModule, type AdminModuleAsyncOptions, type AdminModuleFactoryOptions, type AdminModuleOptions, type AdminModuleOptionsFactory, type AdminOperation, type AdminResourceAuth, type AdminResponse, type BuiltInAuthOptions, type ChartWidget, ConstraintError, type ConstraintKind, type CountWidget, type DashboardWidget, type ErrorResponse, type FieldDto, type FieldKind, type FieldKindDto, type FieldMetadata, FieldNotFoundError, type FieldOverride, type FieldWidget, type FilterOperator, type FilterRule, ForbiddenError, InvalidQueryError, type ListQuery, type ListWidget, type MetadataDto, type ModelDto, type ModelMetadata, ModelNotFoundError, type ModelOverride, type ModelOverrides, NestAdminError, type OrmAdapter, type Page, type PageMeta, type RecordData, type RecordId, RecordNotFoundError, type RelationCardinality, type RelationDto, type RelationMetadata, type ResourceAuthorization, type SortDirection, type SortRule, type StatResult, type StatWidget, type SuccessResponse, UnauthorizedError, ValidationError, type WidgetSpan, adminAccountOf, builtInAuth, generateSessionSecret, hashAdminPassword, isNestAdminError, unsafeAllowAllRequests, verifyAdminPassword };