@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,585 @@
1
+ /**
2
+ * Normalised, ORM-independent description of a model and its fields.
3
+ *
4
+ * Every ORM adapter translates its own schema representation (Prisma DMMF,
5
+ * TypeORM entity metadata, a Drizzle table object, ...) into these shapes.
6
+ * Nothing downstream - the CRUD engine, the HTTP API, the admin UI - is
7
+ * allowed to look at anything else.
8
+ *
9
+ * @experimental Draft contract. Expected to change during MVP implementation.
10
+ */
11
+ /** ORM-independent classification of a scalar or relation field. */
12
+ type FieldKind = 'string' | 'number' | 'boolean' | 'datetime' | 'enum' | 'json' | 'relation'
13
+ /** The adapter recognised the field but cannot map it onto a known kind. */
14
+ | 'unknown';
15
+ /** Cardinality of a relation from the owning model's point of view. */
16
+ type RelationCardinality = 'one' | 'many';
17
+ /**
18
+ * A relation, and how to act on it.
19
+ *
20
+ * `from` and `to` are what turn a relation from something an admin can only
21
+ * display into something it can filter and write. A to-one relation is stored
22
+ * as an ordinary scalar column - `Post.authorId` - and that column is what a
23
+ * query has to be expressed in terms of. Without knowing its name, a filter on
24
+ * `author` cannot be translated, and a form has no field to submit.
25
+ *
26
+ * Both are absent on to-many relations, which have no column on this side.
27
+ */
28
+ interface RelationMetadata {
29
+ /** `name` of the {@link ModelMetadata} on the other side of the relation. */
30
+ readonly targetModel: string;
31
+ readonly cardinality: RelationCardinality;
32
+ /**
33
+ * Scalar field on **this** model holding the foreign key, for a to-one
34
+ * relation - `authorId` on `Post.author`.
35
+ *
36
+ * Absent when the relation has no column on this side: every to-many, and
37
+ * the non-owning half of a one-to-one.
38
+ */
39
+ readonly from?: string;
40
+ /** Field on the target model that `from` points at - usually its id. */
41
+ readonly to?: string;
42
+ /**
43
+ * Name shared by both halves of the relation.
44
+ *
45
+ * The only reliable way to pair `User.posts` with `Post.author`, which two
46
+ * things need. Distinguishing a many-to-many from a one-to-many requires
47
+ * looking at the other side - both are `'many'` from here, but only one has
48
+ * no column anywhere. And knowing whether a child's key is required decides
49
+ * whether it can be detached at all.
50
+ *
51
+ * Two relations between the same pair of models are told apart by it too:
52
+ * `Post.author` and `Post.reviewer` both target `User`.
53
+ */
54
+ readonly name?: string;
55
+ }
56
+ interface FieldMetadata {
57
+ readonly name: string;
58
+ readonly kind: FieldKind;
59
+ /** Part of the model's primary key. */
60
+ readonly isId: boolean;
61
+ readonly isRequired: boolean;
62
+ readonly isUnique: boolean;
63
+ /** The field holds a list of {@link FieldKind} values. */
64
+ readonly isList: boolean;
65
+ /**
66
+ * The value is produced by the database or the ORM and is not asked of the
67
+ * user - `@default(cuid())`, `@default(now())`, `@default(autoincrement())`,
68
+ * `@updatedAt`. Such fields are displayed but not editable.
69
+ *
70
+ * This is NOT "has a default". A field with a literal default
71
+ * (`active Boolean @default(true)`) is an ordinary editable field that
72
+ * happens to arrive pre-filled; see {@link FieldMetadata.defaultValue}.
73
+ *
74
+ * NAME COLLISION - read before implementing an adapter. Prisma's DMMF also
75
+ * has a field called `isGenerated`, and it does NOT mean this. Measured
76
+ * against Prisma 7.10.0, DMMF reports `isGenerated: false` for
77
+ * `id String @id @default(cuid())`. Mapping it across directly produces
78
+ * editable primary keys. The correct derivation - a *function* default, or
79
+ * an updated-at column - is in `packages/prisma/src/metadata/to-metadata.ts`
80
+ * and `packages/drizzle/src/metadata/to-metadata.ts`, which state it in each
81
+ * ORM's own terms.
82
+ */
83
+ readonly isGenerated: boolean;
84
+ /**
85
+ * Accepted on a write, never returned on a read.
86
+ *
87
+ * Set by `writeOnly` in the configuration. A password is the reason it
88
+ * exists: it has to be typed into a form and must never come back out, and
89
+ * `hidden` cannot express that - it refuses the field in both directions, so
90
+ * a hidden password column leaves no way to set one.
91
+ *
92
+ * Enforced twice, deliberately: the field is left out of the columns the
93
+ * adapter is asked for, *and* out of the projection applied to whatever comes
94
+ * back. One of those is enough; two is what it takes for a future adapter
95
+ * that ignores the field scope not to become a leak.
96
+ */
97
+ readonly writeOnly?: boolean;
98
+ /**
99
+ * Literal default the admin should pre-fill on create, when the schema
100
+ * declares one (`@default(true)`, `@default(0)`, `@default("USER")`).
101
+ *
102
+ * Absent for generated values: there is no literal to pre-fill for
103
+ * `@default(now())`, and {@link FieldMetadata.isGenerated} is `true` instead.
104
+ */
105
+ readonly defaultValue?: unknown;
106
+ /** Populated when `kind` is `'enum'`. */
107
+ readonly enumValues?: readonly string[];
108
+ /** Populated when `kind` is `'relation'`. */
109
+ readonly relation?: RelationMetadata;
110
+ }
111
+ interface ModelMetadata {
112
+ /** Adapter-facing identifier, e.g. the Prisma model name `User`. */
113
+ readonly name: string;
114
+ /**
115
+ * Field names forming the primary key. Modelled as a list rather than a
116
+ * single `id` so composite keys do not require a breaking change later,
117
+ * even though the MVP will only support single-column keys.
118
+ */
119
+ readonly primaryKey: readonly string[];
120
+ readonly fields: readonly FieldMetadata[];
121
+ /**
122
+ * Field that names a record of this model in one line, when the application
123
+ * has declared one.
124
+ *
125
+ * A slot rather than a value: left unset, `displayFieldFor` works it out from
126
+ * the fields. It is here so that a declared choice travels with the model and
127
+ * reaches the adapter and the metadata document alike, without either of them
128
+ * having to read configuration.
129
+ */
130
+ readonly displayField?: string;
131
+ }
132
+
133
+ /**
134
+ * Where admin accounts live, as a contract.
135
+ *
136
+ * ## Why this exists at all
137
+ *
138
+ * Until 0.9.0 the answer to "who may open the admin?" was always the host
139
+ * application's: it already had sessions, and `AdminAuth` asked it one
140
+ * question. That is still right for a team that has an identity system, and
141
+ * nothing about it changes.
142
+ *
143
+ * It is a wall for everyone else. An application with no login of its own had
144
+ * to write a password hash, a session cookie and a form before the admin could
145
+ * go anywhere near production - which is a strange thing to ask of a package
146
+ * whose whole claim is that you do not build an admin.
147
+ *
148
+ * ## Why it is a contract rather than a table
149
+ *
150
+ * The same reason `OrmAdapter` is. An admin whose accounts can only live in
151
+ * Prisma has learned about Prisma, and the second ORM would find out the hard
152
+ * way. Everything here is plain data and promises; nothing knows what a
153
+ * database is.
154
+ *
155
+ * ## These accounts are not the application's users
156
+ *
157
+ * Deliberately, and this is the point most worth getting right. The people who
158
+ * administer a system are usually not rows in the table they administer, and
159
+ * conflating the two means a customer record with a password that opens the
160
+ * admin. The store is separate storage - a different model, or a different
161
+ * database entirely - and the admin never reads or writes the application's
162
+ * own users to decide who may sign in.
163
+ */
164
+ /** One account that may sign in to the admin. */
165
+ interface AdminAccount {
166
+ readonly id: string;
167
+ /**
168
+ * What is typed into the login form.
169
+ *
170
+ * Called `email` because that is what it almost always is, and a name people
171
+ * recognise is worth more than one that covers a case nobody has. A store is
172
+ * free to hold usernames in it.
173
+ */
174
+ readonly email: string;
175
+ /** Shown in the interface. Falls back to the email when absent. */
176
+ readonly name?: string | undefined;
177
+ /**
178
+ * The stored password hash, in whatever form the hasher produced.
179
+ *
180
+ * Read by the sign-in check and by nothing else. It must never reach a
181
+ * response, and the account the interface is told about is a projection that
182
+ * does not include it.
183
+ */
184
+ readonly passwordHash: string;
185
+ /**
186
+ * Suspended without being deleted.
187
+ *
188
+ * Distinct from removing the row: an account that has done things is worth
189
+ * keeping for the record, and "cannot sign in" is not the same fact as
190
+ * "never existed".
191
+ */
192
+ readonly disabled?: boolean | undefined;
193
+ }
194
+ /**
195
+ * How the admin reaches its accounts.
196
+ *
197
+ * Read-only by design. Creating and editing accounts is the application's
198
+ * business: it owns the storage, it knows whether that is a migration, a seed
199
+ * script or a form somewhere else, and an admin that could mint its own
200
+ * administrators is an escalation waiting for its first mistake.
201
+ */
202
+ interface AdminAccountStore {
203
+ /**
204
+ * Find an account by what was typed into the login form.
205
+ *
206
+ * Matching is the store's decision, and it should be case-insensitive on the
207
+ * local part in practice: someone who registered as `Ada@example.com` will
208
+ * type `ada@example.com` eventually.
209
+ *
210
+ * Returns `null` when there is none. The caller must not behave observably
211
+ * differently for `null` than for a wrong password - see the sign-in code.
212
+ */
213
+ findByEmail(email: string): Promise<AdminAccount | null>;
214
+ /**
215
+ * Find an account by its id, for a request that arrives with a session.
216
+ *
217
+ * Called on every authenticated request, so it should be cheap. It is also
218
+ * what makes a disabled or deleted account stop working immediately rather
219
+ * than when its session happens to expire.
220
+ */
221
+ findById(id: string): Promise<AdminAccount | null>;
222
+ /**
223
+ * How many accounts exist.
224
+ *
225
+ * Used once, at startup, to say so when the answer is zero - an admin nobody
226
+ * can sign in to is a configuration mistake that otherwise announces itself
227
+ * as a login form that rejects everything.
228
+ */
229
+ count(): Promise<number>;
230
+ /**
231
+ * Note that an account signed in. Optional.
232
+ *
233
+ * A store that does not care about this can leave it out; the sign-in path
234
+ * does not wait for it and a failure is logged rather than surfaced, because
235
+ * "your login worked but we could not write down that it did" is not
236
+ * something the person signing in can act on.
237
+ */
238
+ recordLogin?(id: string): Promise<void>;
239
+ /**
240
+ * What this store reads, for diagnostics. Optional.
241
+ *
242
+ * A model name, a table, a directory - whatever names the storage in a way a
243
+ * person would recognise. It exists so a startup check can say something
244
+ * useful rather than something generic: an admin that exposes its own
245
+ * account model as an editable resource is an escalation, and a warning that
246
+ * cannot name the model is a warning nobody acts on.
247
+ *
248
+ * Never used to decide anything, and never sent to a client.
249
+ */
250
+ readonly describes?: string;
251
+ }
252
+
253
+ /**
254
+ * ORM-independent query description.
255
+ *
256
+ * The admin UI and the HTTP layer speak only this vocabulary; each adapter is
257
+ * responsible for translating it into its own query language.
258
+ *
259
+ * @experimental Draft contract. Expected to change during MVP implementation.
260
+ */
261
+ type SortDirection = 'asc' | 'desc';
262
+ interface SortRule {
263
+ readonly field: string;
264
+ readonly direction: SortDirection;
265
+ }
266
+ /**
267
+ * The deliberately small operator set the MVP targets. Anything richer
268
+ * (nested relation filters, OR/AND trees, full-text) is a later concern and
269
+ * should extend this union rather than bypass it.
270
+ */
271
+ type FilterOperator = 'eq' | 'ne' | 'contains' | 'startsWith' | 'endsWith' | 'gt' | 'gte' | 'lt' | 'lte' | 'in';
272
+ interface FilterRule {
273
+ readonly field: string;
274
+ readonly operator: FilterOperator;
275
+ readonly value: unknown;
276
+ }
277
+ /** Page-number based pagination. Cursor pagination is a later addition. */
278
+ interface ListQuery {
279
+ readonly page?: number;
280
+ readonly perPage?: number;
281
+ readonly sort?: readonly SortRule[];
282
+ readonly filters?: readonly FilterRule[];
283
+ /** Free-text term the adapter applies across searchable string fields. */
284
+ readonly search?: string;
285
+ /**
286
+ * The fields this query may touch, and the only ones it should return.
287
+ *
288
+ * Set by the caller that knows which fields the admin exposes - the adapter
289
+ * reads a schema, not a configuration. Without it, a field the application
290
+ * hid would still be searched by free text, sortable, filterable and
291
+ * returned, because from the adapter's side it is an ordinary column.
292
+ *
293
+ * Omitted means "every field the model has".
294
+ */
295
+ readonly fields?: readonly string[];
296
+ }
297
+ interface Page<T> {
298
+ readonly data: readonly T[];
299
+ readonly total: number;
300
+ readonly page: number;
301
+ readonly perPage: number;
302
+ }
303
+
304
+ /**
305
+ * The single seam between Nest Admin and any ORM.
306
+ *
307
+ * Adding support for a new ORM means writing one implementation of
308
+ * {@link OrmAdapter} and nothing else. Core, the NestJS integration, the HTTP
309
+ * contract and the admin UI stay untouched.
310
+ *
311
+ * @experimental Draft contract. Expected to change during MVP implementation.
312
+ */
313
+
314
+ /**
315
+ * Primary key value of a single record. Composite keys are represented by
316
+ * {@link ModelMetadata.primaryKey}; supporting them at this level is a
317
+ * post-MVP change.
318
+ */
319
+ type RecordId = string | number;
320
+ /** An untyped record as it crosses the adapter boundary. */
321
+ type RecordData = Record<string, unknown>;
322
+ interface OrmAdapter {
323
+ /** Stable identifier used in diagnostics, e.g. `'prisma'`. */
324
+ readonly name: string;
325
+ /**
326
+ * Discover the models the adapter can serve. Asynchronous because an adapter
327
+ * may need to read a schema file or import a generated client.
328
+ */
329
+ getModels(): Promise<readonly ModelMetadata[]>;
330
+ list(model: string, query: ListQuery): Promise<Page<RecordData>>;
331
+ findOne(model: string, id: RecordId): Promise<RecordData | null>;
332
+ create(model: string, data: RecordData): Promise<RecordData>;
333
+ update(model: string, id: RecordId, data: RecordData): Promise<RecordData>;
334
+ delete(model: string, id: RecordId): Promise<void>;
335
+ /**
336
+ * A page of the records on the far side of a to-many relation.
337
+ *
338
+ * Paginated for the same reason a list is: the number of children is a
339
+ * property of the data, not of the schema, and a parent with fifty thousand
340
+ * of them must not be a page that never loads.
341
+ *
342
+ * Kept separate from `list` rather than expressed as a filter because a
343
+ * many-to-many has no column to filter on - the link lives in a join table.
344
+ * A one-to-many could be asked for either way; going through one method means
345
+ * the caller does not have to know which it is looking at.
346
+ */
347
+ listRelated(model: string, id: RecordId, relationField: string, query: ListQuery): Promise<Page<RecordData>>;
348
+ /**
349
+ * Link an existing record to this one.
350
+ *
351
+ * Across a many-to-many this adds a row to the join table and changes
352
+ * neither record. Across a one-to-many it rewrites the child's foreign key,
353
+ * which also **removes it from whatever parent held it** - the same operation
354
+ * with a consequence the caller should have been told about. Deciding whether
355
+ * to warn is the transport layer's job; the adapter performs what it is asked.
356
+ */
357
+ attachRelated(model: string, id: RecordId, relationField: string, targetId: RecordId): Promise<void>;
358
+ /**
359
+ * Unlink a record from this one, without deleting either.
360
+ *
361
+ * Across a one-to-many this clears the child's foreign key, which is
362
+ * impossible when that column is required - see `detachBlockedReason`. The
363
+ * adapter may assume the caller has checked, and will surface the database's
364
+ * own refusal if it has not.
365
+ */
366
+ detachRelated(model: string, id: RecordId, relationField: string, targetId: RecordId): Promise<void>;
367
+ }
368
+
369
+ /**
370
+ * Framework error vocabulary.
371
+ *
372
+ * Deliberately small. These exist so that adapters raise ORM-independent
373
+ * errors and the transport layer can map them to status codes without knowing
374
+ * which ORM produced them. Resist growing this taxonomy - add a new type only
375
+ * when a caller genuinely needs to branch on it.
376
+ *
377
+ * ## Why these are not identified with `instanceof`
378
+ *
379
+ * A published bundle can contain more than one copy of this module. The
380
+ * package ships two CommonJS entrypoints and each inlines its own copy of
381
+ * Core, so an error thrown inside the Prisma adapter is an instance of a
382
+ * *different* `FieldNotFoundError` class than the one the exception filter
383
+ * holds. `instanceof` compares class identity, so it answered `false` and
384
+ * every adapter-raised error was mapped to a generic 500 - a caller who
385
+ * mistyped a sort field got "internal error" instead of "unknown field".
386
+ *
387
+ * That was invisible to this repository's own tests, which resolve Core to a
388
+ * single source module, and only appeared when the built package was installed
389
+ * and run. So errors are identified by *value* rather than identity: a
390
+ * `Symbol.for` brand, which duplicate copies agree on by definition, plus a
391
+ * stable `kind` string. Neither depends on which copy created the object.
392
+ *
393
+ * `scripts/verify-packed-consumer.mjs` asserts the arrangement every release:
394
+ * one shared copy in ESM, one per entrypoint in CJS. If that ever changes, the
395
+ * count changes there first.
396
+ *
397
+ * @experimental Draft contract. Expected to change during MVP implementation.
398
+ */
399
+ /**
400
+ * Stable discriminator for each error type.
401
+ *
402
+ * A declared string rather than the class, so it survives duplicate bundles,
403
+ * and rather than `name`, so it survives minification.
404
+ */
405
+ type AdminErrorKind = 'unauthorized' | 'forbidden' | 'model-not-found' | 'field-not-found' | 'record-not-found' | 'invalid-query'
406
+ /** Application code refused the input. Its message reaches the client. */
407
+ | 'validation'
408
+ /** The database refused the write: unique, foreign key, or required. */
409
+ | 'constraint' | 'adapter'
410
+ /** A subclass that declared no kind of its own. Treated as internal. */
411
+ | 'unknown';
412
+ /**
413
+ * Base error type. Every error raised by Nest Admin extends it so that the
414
+ * NestJS integration can distinguish framework errors from application errors
415
+ * without depending on concrete subclasses.
416
+ */
417
+ declare class NestAdminError extends Error {
418
+ /**
419
+ * Which error this is.
420
+ *
421
+ * Subclasses override it with a literal. The base value covers anything that
422
+ * extends this class without declaring one - the Prisma schema errors, for
423
+ * instance - which the transport layer treats as internal.
424
+ */
425
+ readonly kind: AdminErrorKind;
426
+ constructor(message: string, options?: {
427
+ cause?: unknown;
428
+ });
429
+ }
430
+
431
+ /**
432
+ * `PrismaAdapter` - the Prisma implementation of Core's `OrmAdapter`.
433
+ *
434
+ * The adapter never constructs a Prisma Client. Prisma 7 builds clients from
435
+ * driver adapters, so only the consuming application knows the provider, the
436
+ * credentials and the connection strategy. We receive a constructed client and
437
+ * use it.
438
+ */
439
+
440
+ interface PrismaAdapterOptions {
441
+ /**
442
+ * A constructed Prisma Client. Owned entirely by the consuming application:
443
+ * the adapter never calls `new PrismaClient()`, because under Prisma 7 the
444
+ * client is built from a driver adapter that only the application can supply.
445
+ */
446
+ readonly client: unknown;
447
+ /**
448
+ * Path to `schema.prisma`, or to a directory of `.prisma` files. When
449
+ * omitted, `prisma/schema.prisma`, `prisma/schema` and `schema.prisma` are
450
+ * tried in that order, relative to `cwd`.
451
+ */
452
+ readonly schemaPath?: string;
453
+ /** Base directory for schema resolution. Defaults to `process.cwd()`. */
454
+ readonly cwd?: string;
455
+ }
456
+ declare class PrismaAdapter implements OrmAdapter {
457
+ #private;
458
+ readonly name = "prisma";
459
+ constructor(options: PrismaAdapterOptions);
460
+ getModels(): Promise<readonly ModelMetadata[]>;
461
+ list(model: string, query: ListQuery): Promise<Page<RecordData>>;
462
+ findOne(model: string, id: RecordId): Promise<RecordData | null>;
463
+ create(model: string, data: RecordData): Promise<RecordData>;
464
+ update(model: string, id: RecordId, data: RecordData): Promise<RecordData>;
465
+ delete(model: string, id: RecordId): Promise<void>;
466
+ /**
467
+ * A page of the records on the far side of a to-many relation.
468
+ *
469
+ * Implemented as an ordinary list of the *target* model with one extra
470
+ * condition, so pagination, sorting, filtering and relation loading all
471
+ * behave exactly as they do on a top-level list. See `to-related-where.ts`.
472
+ */
473
+ listRelated(model: string, id: RecordId, relationField: string, query: ListQuery): Promise<Page<RecordData>>;
474
+ attachRelated(model: string, id: RecordId, relationField: string, targetId: RecordId): Promise<void>;
475
+ detachRelated(model: string, id: RecordId, relationField: string, targetId: RecordId): Promise<void>;
476
+ }
477
+
478
+ /** Raised when the Prisma schema cannot be located or read. */
479
+ declare class PrismaSchemaNotFoundError extends NestAdminError {
480
+ readonly triedPaths: readonly string[];
481
+ constructor(triedPaths: readonly string[], explicit: boolean);
482
+ }
483
+ /** Raised when Prisma rejects the schema. Carries Prisma's own validation text. */
484
+ declare class PrismaSchemaInvalidError extends NestAdminError {
485
+ readonly prismaMessage: string;
486
+ constructor(prismaMessage: string, options?: {
487
+ cause?: unknown;
488
+ });
489
+ }
490
+
491
+ /**
492
+ * Prisma version gate.
493
+ *
494
+ * Phase 1 established that `@prisma/get-dmmf` is pinned exactly and enforces
495
+ * *its own* Prisma version's schema rules: given a Prisma 6 schema, the 7.x
496
+ * parser rejects `url` inside `datasource` even though the schema is perfectly
497
+ * valid for that consumer. Without a gate, that surfaces as a confusing
498
+ * "Prisma rejected the schema" error pointing at the user's own valid file.
499
+ *
500
+ * The gate turns that into a statement about versions.
501
+ *
502
+ * ## Two deliberate design choices
503
+ *
504
+ * **It fails open on detection.** The client version is read from
505
+ * `client._clientVersion`, an underscore-prefixed internal. If Prisma renames
506
+ * or removes it, the gate silently does nothing rather than breaking every
507
+ * consumer on an otherwise-fine upgrade. A version check that itself becomes
508
+ * the outage is worse than no version check.
509
+ *
510
+ * **It compares majors only.** Minor and patch releases have not changed the
511
+ * schema language; majors have. Pinning tighter would produce false alarms on
512
+ * every routine bump.
513
+ *
514
+ * This lives in `packages/prisma`, not Core - Core must never learn what
515
+ * Prisma is.
516
+ */
517
+
518
+ /** Raised when the consumer's Prisma Client major is outside the tested range. */
519
+ declare class PrismaVersionUnsupportedError extends NestAdminError {
520
+ readonly clientVersion: string;
521
+ readonly supportedMajors: readonly number[];
522
+ constructor(clientVersion: string, supportedMajors: readonly number[]);
523
+ }
524
+
525
+ /**
526
+ * Admin accounts, in Prisma.
527
+ *
528
+ * ## A model of its own
529
+ *
530
+ * The default is `AdminAccount`, and that default is the design rather than a
531
+ * placeholder. The people who administer a system are usually not rows in the
532
+ * table they administer, and pointing this at the application's `User` would
533
+ * mean every customer record carries a password that opens the admin - which is
534
+ * a decision nobody makes on purpose and several people make by accident.
535
+ *
536
+ * The model name is configurable because some applications already have a
537
+ * `Staff` or an `Operator`. Pointing it at `User` is possible and is a choice,
538
+ * not a default.
539
+ *
540
+ * ## What it does not do
541
+ *
542
+ * Create, update, delete. The store contract is read-only, and this implements
543
+ * only what it declares: an admin that could mint its own administrators is an
544
+ * escalation waiting for its first mistake in a policy. Seeding the first
545
+ * account is the application's job, with `hashAdminPassword`.
546
+ *
547
+ * ## The account model should not be a resource
548
+ *
549
+ * Nothing here can arrange that - which models the admin exposes is the
550
+ * module's business - so it is the one thing a consumer has to remember:
551
+ *
552
+ * ```ts
553
+ * resources: { exclude: ['AdminAccount'] }
554
+ * ```
555
+ *
556
+ * Without it, anyone who may edit that model can grant themselves whatever the
557
+ * admin can do. `builtInAuth` warns at startup when it sees the account model
558
+ * among the exposed resources.
559
+ */
560
+
561
+ interface PrismaAccountStoreOptions {
562
+ /** A constructed Prisma Client - the same one the adapter is given. */
563
+ readonly client: unknown;
564
+ /** The model holding admin accounts. `AdminAccount` by default. */
565
+ readonly model?: string;
566
+ /**
567
+ * Column names, where they differ from the defaults.
568
+ *
569
+ * A mapping rather than a required schema: an application that already has a
570
+ * `Staff` table with `login` and `hash` should not have to migrate it to use
571
+ * this.
572
+ */
573
+ readonly fields?: {
574
+ readonly id?: string;
575
+ readonly email?: string;
576
+ readonly name?: string;
577
+ readonly passwordHash?: string;
578
+ readonly disabled?: string;
579
+ /** Written on a successful sign-in, when the column exists. */
580
+ readonly lastLoginAt?: string;
581
+ };
582
+ }
583
+ declare function prismaAccountStore(options: PrismaAccountStoreOptions): AdminAccountStore;
584
+
585
+ export { type PrismaAccountStoreOptions, PrismaAdapter, type PrismaAdapterOptions, PrismaSchemaInvalidError, PrismaSchemaNotFoundError, PrismaVersionUnsupportedError, prismaAccountStore };