@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.
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/dist/admin-ui/assets/index-AyWOamlt.js +50 -0
- package/dist/admin-ui/assets/index-AyWOamlt.js.map +1 -0
- package/dist/admin-ui/assets/index-D4Eh84eD.css +2 -0
- package/dist/admin-ui/index.html +14 -0
- package/dist/chunk-7IXLRGGQ.js +356 -0
- package/dist/chunk-7IXLRGGQ.js.map +1 -0
- package/dist/drizzle.cjs +895 -0
- package/dist/drizzle.cjs.map +1 -0
- package/dist/drizzle.d.cts +335 -0
- package/dist/drizzle.d.ts +335 -0
- package/dist/drizzle.js +756 -0
- package/dist/drizzle.js.map +1 -0
- package/dist/index.cjs +3247 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1652 -0
- package/dist/index.d.ts +1652 -0
- package/dist/index.js +2901 -0
- package/dist/index.js.map +1 -0
- package/dist/prisma.cjs +1159 -0
- package/dist/prisma.cjs.map +1 -0
- package/dist/prisma.d.cts +585 -0
- package/dist/prisma.d.ts +585 -0
- package/dist/prisma.js +995 -0
- package/dist/prisma.js.map +1 -0
- package/package.json +130 -0
package/dist/prisma.d.ts
ADDED
|
@@ -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 };
|