@hypersonic-js/admin 0.2.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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/dist/constants.d.ts +6 -0
  3. package/dist/constants.d.ts.map +1 -0
  4. package/dist/constants.js +6 -0
  5. package/dist/constants.js.map +1 -0
  6. package/dist/crud/pagination.d.ts +11 -0
  7. package/dist/crud/pagination.d.ts.map +1 -0
  8. package/dist/crud/pagination.js +31 -0
  9. package/dist/crud/pagination.js.map +1 -0
  10. package/dist/crud/query.d.ts +64 -0
  11. package/dist/crud/query.d.ts.map +1 -0
  12. package/dist/crud/query.js +137 -0
  13. package/dist/crud/query.js.map +1 -0
  14. package/dist/crud/router.d.ts +49 -0
  15. package/dist/crud/router.d.ts.map +1 -0
  16. package/dist/crud/router.js +335 -0
  17. package/dist/crud/router.js.map +1 -0
  18. package/dist/dmmf/fields.d.ts +37 -0
  19. package/dist/dmmf/fields.d.ts.map +1 -0
  20. package/dist/dmmf/fields.js +88 -0
  21. package/dist/dmmf/fields.js.map +1 -0
  22. package/dist/dmmf/parser.d.ts +15 -0
  23. package/dist/dmmf/parser.d.ts.map +1 -0
  24. package/dist/dmmf/parser.js +54 -0
  25. package/dist/dmmf/parser.js.map +1 -0
  26. package/dist/index.d.ts +5 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +4 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/middleware/auth.d.ts +8 -0
  31. package/dist/middleware/auth.d.ts.map +1 -0
  32. package/dist/middleware/auth.js +21 -0
  33. package/dist/middleware/auth.js.map +1 -0
  34. package/dist/mount.d.ts +4 -0
  35. package/dist/mount.d.ts.map +1 -0
  36. package/dist/mount.js +21 -0
  37. package/dist/mount.js.map +1 -0
  38. package/dist/scaffold/index.d.ts +14 -0
  39. package/dist/scaffold/index.d.ts.map +1 -0
  40. package/dist/scaffold/index.js +35 -0
  41. package/dist/scaffold/index.js.map +1 -0
  42. package/dist/scaffold/templates.d.ts +19 -0
  43. package/dist/scaffold/templates.d.ts.map +1 -0
  44. package/dist/scaffold/templates.js +371 -0
  45. package/dist/scaffold/templates.js.map +1 -0
  46. package/dist/templates/Dashboard.tsx +40 -0
  47. package/dist/templates/ModelForm.tsx +288 -0
  48. package/dist/templates/ModelIndex.tsx +142 -0
  49. package/dist/templates/UserCreate.tsx +189 -0
  50. package/dist/types.d.ts +159 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +3 -0
  53. package/dist/types.js.map +1 -0
  54. package/package.json +60 -0
  55. package/templates/Dashboard.tsx +40 -0
  56. package/templates/ModelForm.tsx +288 -0
  57. package/templates/ModelIndex.tsx +142 -0
  58. package/templates/UserCreate.tsx +189 -0
@@ -0,0 +1,335 @@
1
+ import { Router } from 'express';
2
+ import { findMany, countRecords, findUnique, createRecord, updateRecord, deleteRecord, fetchRelatedOptions, coerceData, } from './query.js';
3
+ import { parsePaginationParams, buildPaginationMeta } from './pagination.js';
4
+ import { MAX_RELATED_OPTIONS } from '../constants.js';
5
+ /**
6
+ * The three fields that Better Auth's createUser API accepts at the top level
7
+ * of its body, plus `role` which is forwarded as a named param rather than via
8
+ * `body.data`. Any formField whose name is NOT in this set is forwarded as a
9
+ * custom field via `body.data`.
10
+ */
11
+ const BETTER_AUTH_CORE_FIELDS = new Set(['name', 'email', 'password', 'role']);
12
+ /**
13
+ * Fetches the first page of <select> options for every FK field in the model's
14
+ * formFields. Uses allMeta (the full unfiltered metadata) so hidden models (e.g.
15
+ * User when showAuthModels is false) can still be queried for their dropdown values.
16
+ *
17
+ * Returns a map of { [fkFieldName]: { options, hasMore } }.
18
+ */
19
+ async function buildRelatedOptions(prisma, model, allMeta) {
20
+ const fkFields = model.formFields.filter((f) => f.isForeignKey && f.relatedModelName !== undefined);
21
+ if (fkFields.length === 0)
22
+ return {};
23
+ const entries = await Promise.all(fkFields.map(async (f) => {
24
+ const relatedMeta = allMeta.find((m) => m.name === f.relatedModelName);
25
+ if (relatedMeta === undefined)
26
+ return [f.name, { options: [], hasMore: false }];
27
+ const options = await fetchRelatedOptions(prisma, relatedMeta, 0);
28
+ return [f.name, { options, hasMore: options.length === MAX_RELATED_OPTIONS }];
29
+ }));
30
+ return Object.fromEntries(entries);
31
+ }
32
+ /**
33
+ * Builds an Express Router containing all admin CRUD routes.
34
+ * The router should be mounted at the admin prefix via app.use(prefix, authMiddleware, router).
35
+ *
36
+ * @param prisma Prisma client instance.
37
+ * @param models Filtered model list (nav-visible models only).
38
+ * @param prefix Route prefix the router is mounted at.
39
+ * @param options Optional configuration — see AdminRouterOptions.
40
+ *
41
+ * Routes (all relative to mount prefix):
42
+ * GET / -> Admin/Dashboard
43
+ * GET /related-options/:relatedModel -> JSON { options, hasMore } for FK load-more
44
+ * GET /:model -> Admin/ModelIndex (paginated list)
45
+ * GET /:model/new -> Admin/UserCreate (when auth.api.createUser present)
46
+ * Admin/ModelForm (create form, otherwise)
47
+ * GET /:model/:id -> Admin/ModelForm (edit form)
48
+ * POST /:model -> Better Auth createUser (when auth.api.createUser present)
49
+ * create record via Prisma, otherwise
50
+ * PATCH /:model/:id -> Better Auth adminUpdateUser (when auth.api.adminUpdateUser present)
51
+ * update record via Prisma, otherwise
52
+ * DELETE /:model/:id -> Better Auth removeUser (when auth.api.removeUser present)
53
+ * delete record via Prisma, otherwise
54
+ */
55
+ export function createAdminRouter(prisma, models, prefix, options = {}) {
56
+ const { allMeta = models, logger, auth, betterAuthUserModel = 'User' } = options;
57
+ const router = Router();
58
+ const modelMap = new Map(models.map((m) => [m.urlSlug, m]));
59
+ const allMetaMap = new Map(allMeta.map((m) => [m.urlSlug, m]));
60
+ const navModels = models.map((m) => ({ name: m.name, urlSlug: m.urlSlug }));
61
+ // Detect if any Better Auth admin methods are available for the user model.
62
+ // Each verb is registered independently — POST when createUser is present,
63
+ // PATCH when adminUpdateUser is present, DELETE when removeUser is present.
64
+ // This prevents guaranteed 500s when the Better Auth admin plugin only
65
+ // exposes a subset of the three methods (a valid partial configuration).
66
+ const betterAuthMeta = auth?.api.createUser !== undefined ||
67
+ auth?.api.adminUpdateUser !== undefined ||
68
+ auth?.api.removeUser !== undefined
69
+ ? models.find((m) => m.name === betterAuthUserModel)
70
+ : undefined;
71
+ // GET / -- Dashboard
72
+ router.get('/', async (_req, res, next) => {
73
+ try {
74
+ const modelCounts = await Promise.all(models.map(async (m) => {
75
+ const count = await countRecords(prisma, m);
76
+ return { name: m.name, urlSlug: m.urlSlug, recordCount: count };
77
+ }));
78
+ res.inertia('Admin/Dashboard', { models: modelCounts, prefix });
79
+ }
80
+ catch (err) {
81
+ next(err);
82
+ }
83
+ });
84
+ // GET /related-options/:relatedModel -- paginated FK dropdown options
85
+ // Registered before /:model/* routes so the literal "related-options" segment
86
+ // is matched before any dynamic model slug.
87
+ router.get('/related-options/:relatedModel', async (req, res, next) => {
88
+ try {
89
+ const meta = allMetaMap.get(req.params['relatedModel']);
90
+ if (meta === undefined) {
91
+ res.status(404).json({ error: 'Not Found' });
92
+ return;
93
+ }
94
+ const rawPage = Number(req.query['page'] ?? 1);
95
+ const page = Number.isFinite(rawPage) && rawPage >= 1 ? rawPage : 1;
96
+ const skip = (page - 1) * MAX_RELATED_OPTIONS;
97
+ const options_ = await fetchRelatedOptions(prisma, meta, skip);
98
+ const hasMore = options_.length === MAX_RELATED_OPTIONS;
99
+ res.json({ options: options_, hasMore });
100
+ }
101
+ catch (err) {
102
+ next(err);
103
+ }
104
+ });
105
+ // ── Better Auth user model routes ─────────────────────────────────────────
106
+ // Registered BEFORE the generic /:model/* routes so they take precedence.
107
+ // Each verb is only mounted when its corresponding auth method is available,
108
+ // so partial Better Auth configurations fall through to the generic Prisma
109
+ // routes for the uncovered verbs. GET /:model (index) and GET /:model/:id
110
+ // (edit) are intentionally NOT overridden — they always use Prisma.
111
+ if (betterAuthMeta !== undefined) {
112
+ const userSlug = betterAuthMeta.urlSlug;
113
+ // GET + POST: only when createUser is available.
114
+ if (auth?.api.createUser !== undefined) {
115
+ const createUser = auth.api.createUser;
116
+ // GET /:userSlug/new -- bespoke user create form.
117
+ // The full model (including formFields) is passed so the UI can render
118
+ // role and any custom fields from metadata rather than hard-coding them.
119
+ router.get(`/${userSlug}/new`, async (_req, res, next) => {
120
+ try {
121
+ res.inertia('Admin/UserCreate', {
122
+ model: betterAuthMeta,
123
+ errors: {},
124
+ models: navModels,
125
+ prefix,
126
+ });
127
+ }
128
+ catch (err) {
129
+ next(err);
130
+ }
131
+ });
132
+ // POST /:userSlug -- create user via Better Auth admin API.
133
+ // name, email, password, and role are forwarded as Better Auth top-level
134
+ // params. Any other formFields are forwarded via body.data so custom
135
+ // required columns on the User model are correctly persisted.
136
+ router.post(`/${userSlug}`, async (req, res, next) => {
137
+ try {
138
+ const body = req.body;
139
+ const role = body['role'];
140
+ const extraData = {};
141
+ for (const f of betterAuthMeta.formFields) {
142
+ if (!BETTER_AUTH_CORE_FIELDS.has(f.name) && body[f.name] !== undefined) {
143
+ extraData[f.name] = body[f.name];
144
+ }
145
+ }
146
+ await createUser({
147
+ body: {
148
+ name: String(body['name'] ?? ''),
149
+ email: String(body['email'] ?? ''),
150
+ password: String(body['password'] ?? ''),
151
+ role: role !== '' && role !== undefined ? String(role) : undefined,
152
+ ...(Object.keys(extraData).length > 0 ? { data: extraData } : {}),
153
+ },
154
+ });
155
+ res.redirect(303, `${prefix}/${userSlug}`);
156
+ }
157
+ catch (err) {
158
+ next(err);
159
+ }
160
+ });
161
+ }
162
+ // PATCH /:userSlug/:id -- only when adminUpdateUser is available.
163
+ // Forwards the incoming request headers so Better Auth can validate the
164
+ // calling admin's session for permission checks.
165
+ if (auth?.api.adminUpdateUser !== undefined) {
166
+ const adminUpdateUser = auth.api.adminUpdateUser;
167
+ router.patch(`/${userSlug}/:id`, async (req, res, next) => {
168
+ try {
169
+ const userId = req.params['id'];
170
+ const coerced = coerceData(req.body, betterAuthMeta);
171
+ await adminUpdateUser({
172
+ body: { userId, data: coerced },
173
+ headers: req.headers,
174
+ });
175
+ res.redirect(303, `${prefix}/${userSlug}`);
176
+ }
177
+ catch (err) {
178
+ next(err);
179
+ }
180
+ });
181
+ }
182
+ // DELETE /:userSlug/:id -- only when removeUser is available.
183
+ // Better Auth's removeUser also revokes all active sessions for the user,
184
+ // which plain prisma.user.delete would not do.
185
+ if (auth?.api.removeUser !== undefined) {
186
+ const removeUser = auth.api.removeUser;
187
+ router.delete(`/${userSlug}/:id`, async (req, res, next) => {
188
+ try {
189
+ const userId = req.params['id'];
190
+ await removeUser({
191
+ body: { userId },
192
+ headers: req.headers,
193
+ });
194
+ res.redirect(303, `${prefix}/${userSlug}`);
195
+ }
196
+ catch (err) {
197
+ next(err);
198
+ }
199
+ });
200
+ }
201
+ }
202
+ // ── Generic CRUD routes ───────────────────────────────────────────────────
203
+ // GET /:model -- Model index (paginated list)
204
+ router.get('/:model', async (req, res, next) => {
205
+ try {
206
+ const model = modelMap.get(req.params['model']);
207
+ if (model === undefined) {
208
+ res.status(404).json({ error: 'Not Found' });
209
+ return;
210
+ }
211
+ const pagination = parsePaginationParams(req.query);
212
+ const { records, total } = await findMany(prisma, model, pagination);
213
+ const paginationMeta = buildPaginationMeta(pagination.page, pagination.perPage, total);
214
+ res.inertia('Admin/ModelIndex', {
215
+ model,
216
+ records,
217
+ pagination: paginationMeta,
218
+ models: navModels,
219
+ prefix,
220
+ });
221
+ }
222
+ catch (err) {
223
+ next(err);
224
+ }
225
+ });
226
+ // GET /:model/new -- Create form
227
+ router.get('/:model/new', async (req, res, next) => {
228
+ try {
229
+ const model = modelMap.get(req.params['model']);
230
+ if (model === undefined) {
231
+ res.status(404).json({ error: 'Not Found' });
232
+ return;
233
+ }
234
+ const relatedOptions = await buildRelatedOptions(prisma, model, allMeta);
235
+ res.inertia('Admin/ModelForm', {
236
+ model,
237
+ record: null,
238
+ relatedOptions,
239
+ errors: {},
240
+ models: navModels,
241
+ prefix,
242
+ });
243
+ }
244
+ catch (err) {
245
+ next(err);
246
+ }
247
+ });
248
+ // GET /:model/:id -- Edit form
249
+ router.get('/:model/:id', async (req, res, next) => {
250
+ try {
251
+ const model = modelMap.get(req.params['model']);
252
+ if (model === undefined) {
253
+ res.status(404).json({ error: 'Not Found' });
254
+ return;
255
+ }
256
+ const record = await findUnique(prisma, model, req.params['id']);
257
+ if (record === null) {
258
+ res.status(404).json({ error: 'Not Found' });
259
+ return;
260
+ }
261
+ const relatedOptions = await buildRelatedOptions(prisma, model, allMeta);
262
+ res.inertia('Admin/ModelForm', {
263
+ model,
264
+ record,
265
+ relatedOptions,
266
+ errors: {},
267
+ models: navModels,
268
+ prefix,
269
+ });
270
+ }
271
+ catch (err) {
272
+ next(err);
273
+ }
274
+ });
275
+ // POST /:model -- Create
276
+ router.post('/:model', async (req, res, next) => {
277
+ try {
278
+ const model = modelMap.get(req.params['model']);
279
+ if (model === undefined) {
280
+ next();
281
+ return;
282
+ }
283
+ await createRecord(prisma, model, req.body);
284
+ res.redirect(303, `${prefix}/${model.urlSlug}`);
285
+ }
286
+ catch (err) {
287
+ next(err);
288
+ }
289
+ });
290
+ // PATCH /:model/:id -- Update
291
+ router.patch('/:model/:id', async (req, res, next) => {
292
+ try {
293
+ const model = modelMap.get(req.params['model']);
294
+ if (model === undefined) {
295
+ next();
296
+ return;
297
+ }
298
+ await updateRecord(prisma, model, req.params['id'], req.body);
299
+ res.redirect(303, `${prefix}/${model.urlSlug}`);
300
+ }
301
+ catch (err) {
302
+ next(err);
303
+ }
304
+ });
305
+ // DELETE /:model/:id -- Delete
306
+ router.delete('/:model/:id', async (req, res, next) => {
307
+ try {
308
+ const model = modelMap.get(req.params['model']);
309
+ if (model === undefined) {
310
+ next();
311
+ return;
312
+ }
313
+ await deleteRecord(prisma, model, req.params['id']);
314
+ res.redirect(303, `${prefix}/${model.urlSlug}`);
315
+ }
316
+ catch (err) {
317
+ next(err);
318
+ }
319
+ });
320
+ // Error handler — logs the error then either redirects back (Inertia) or
321
+ // returns a clean 500 without leaking internal details.
322
+ const errorHandler = (err, req, res, _next) => {
323
+ logger?.error({ err, method: req.method, url: req.url }, 'Admin request error');
324
+ if (req.headers['x-inertia']) {
325
+ const referer = req.headers['referer'];
326
+ const redirectUrl = typeof referer === 'string' && referer.length > 0 ? referer : `${prefix}/`;
327
+ res.redirect(303, redirectUrl);
328
+ return;
329
+ }
330
+ res.status(500).json({ error: 'Internal Server Error' });
331
+ };
332
+ router.use(errorHandler);
333
+ return router;
334
+ }
335
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/crud/router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { DmmfField, DmmfEnum, AdminFieldMeta, AdminFieldKind } from '../types.js';
2
+ /** Maps a DMMF field kind to our simplified AdminFieldKind. */
3
+ export declare function classifyField(field: DmmfField): AdminFieldKind;
4
+ /**
5
+ * Returns true for fields that are auto-managed by Prisma or the database
6
+ * and should therefore be excluded from admin create/edit forms.
7
+ * Covers:
8
+ * - field.isReadOnly — Prisma's authoritative flag, set on FK scalars and
9
+ * any other field Prisma considers unwritable directly
10
+ * - field.isUpdatedAt — @updatedAt fields managed by the Prisma client
11
+ * - AUTO_MANAGED_FIELD_NAMES — convention-based names (createdAt / updatedAt)
12
+ * for schemas where Prisma does not set the flags
13
+ * (e.g. Better Auth-managed timestamps)
14
+ */
15
+ export declare function isReadOnlyField(field: DmmfField): boolean;
16
+ /**
17
+ * Chooses the most human-readable field name for list view labels.
18
+ * Tries a priority list of common names; falls back to the id field.
19
+ */
20
+ export declare function getDisplayField(fields: AdminFieldMeta[]): string;
21
+ /**
22
+ * Returns the subset of fields suitable for table list columns.
23
+ * Excludes relations, list fields, and overly long text fields.
24
+ * Capped at 6 columns to keep tables readable.
25
+ */
26
+ export declare function getListFields(fields: AdminFieldMeta[]): AdminFieldMeta[];
27
+ /**
28
+ * Returns the subset of fields to show in create/edit forms.
29
+ * Excludes relations, read-only fields, and auto-id fields.
30
+ */
31
+ export declare function getFormFields(fields: AdminFieldMeta[]): AdminFieldMeta[];
32
+ /**
33
+ * Maps a raw DMMF field to an AdminFieldMeta, looking up enum values
34
+ * from the provided enum definitions.
35
+ */
36
+ export declare function mapField(field: DmmfField, enums: DmmfEnum[]): AdminFieldMeta;
37
+ //# sourceMappingURL=fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../../src/dmmf/fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAOtF,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,cAAc,CAI9D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAIzD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,CAOhE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CAKxE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CAOxE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,cAAc,CAiB5E"}
@@ -0,0 +1,88 @@
1
+ import { DISPLAY_FIELD_CANDIDATES, AUTO_MANAGED_FIELD_NAMES, LARGE_TEXT_FIELD_NAMES, } from '../constants.js';
2
+ /** Maps a DMMF field kind to our simplified AdminFieldKind. */
3
+ export function classifyField(field) {
4
+ if (field.kind === 'object')
5
+ return 'relation';
6
+ if (field.kind === 'enum')
7
+ return 'enum';
8
+ return 'scalar';
9
+ }
10
+ /**
11
+ * Returns true for fields that are auto-managed by Prisma or the database
12
+ * and should therefore be excluded from admin create/edit forms.
13
+ * Covers:
14
+ * - field.isReadOnly — Prisma's authoritative flag, set on FK scalars and
15
+ * any other field Prisma considers unwritable directly
16
+ * - field.isUpdatedAt — @updatedAt fields managed by the Prisma client
17
+ * - AUTO_MANAGED_FIELD_NAMES — convention-based names (createdAt / updatedAt)
18
+ * for schemas where Prisma does not set the flags
19
+ * (e.g. Better Auth-managed timestamps)
20
+ */
21
+ export function isReadOnlyField(field) {
22
+ if (field.isReadOnly)
23
+ return true;
24
+ if (field.isUpdatedAt)
25
+ return true;
26
+ return AUTO_MANAGED_FIELD_NAMES.includes(field.name);
27
+ }
28
+ /**
29
+ * Chooses the most human-readable field name for list view labels.
30
+ * Tries a priority list of common names; falls back to the id field.
31
+ */
32
+ export function getDisplayField(fields) {
33
+ for (const candidate of DISPLAY_FIELD_CANDIDATES) {
34
+ const found = fields.find((f) => f.name === candidate && f.kind === 'scalar');
35
+ if (found !== undefined)
36
+ return found.name;
37
+ }
38
+ const idField = fields.find((f) => f.isId);
39
+ return idField?.name ?? 'id';
40
+ }
41
+ /**
42
+ * Returns the subset of fields suitable for table list columns.
43
+ * Excludes relations, list fields, and overly long text fields.
44
+ * Capped at 6 columns to keep tables readable.
45
+ */
46
+ export function getListFields(fields) {
47
+ return fields
48
+ .filter((f) => f.kind === 'scalar' && !f.isList)
49
+ .filter((f) => !LARGE_TEXT_FIELD_NAMES.includes(f.name))
50
+ .slice(0, 6);
51
+ }
52
+ /**
53
+ * Returns the subset of fields to show in create/edit forms.
54
+ * Excludes relations, read-only fields, and auto-id fields.
55
+ */
56
+ export function getFormFields(fields) {
57
+ return fields.filter((f) => {
58
+ if (f.kind === 'relation')
59
+ return false;
60
+ if (f.isReadOnly)
61
+ return false;
62
+ if (f.isId && f.hasDefault)
63
+ return false;
64
+ return true;
65
+ });
66
+ }
67
+ /**
68
+ * Maps a raw DMMF field to an AdminFieldMeta, looking up enum values
69
+ * from the provided enum definitions.
70
+ */
71
+ export function mapField(field, enums) {
72
+ const kind = classifyField(field);
73
+ const enumDef = kind === 'enum' ? enums.find((e) => e.name === field.type) : undefined;
74
+ return {
75
+ name: field.name,
76
+ prismaType: field.type,
77
+ kind,
78
+ isRequired: field.isRequired,
79
+ isId: field.isId,
80
+ isUnique: field.isUnique,
81
+ hasDefault: field.hasDefaultValue,
82
+ isReadOnly: isReadOnlyField(field),
83
+ isList: field.isList,
84
+ relationTo: field.kind === 'object' ? field.type : undefined,
85
+ enumValues: enumDef?.values.map((v) => v.name),
86
+ };
87
+ }
88
+ //# sourceMappingURL=fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fields.js","sourceRoot":"","sources":["../../src/dmmf/fields.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { DmmfDocument, AdminModelMeta, AdminOptions } from '../types.js';
2
+ /**
3
+ * Converts a model name to a plural display name.
4
+ * Handles the most common English pluralisation rules.
5
+ */
6
+ export declare function toDisplayName(name: string): string;
7
+ /**
8
+ * Parses the Prisma DMMF document into an array of AdminModelMeta objects,
9
+ * applying visibility rules (hidden models) in the process.
10
+ *
11
+ * @param dmmf - The DMMF document from `Prisma.dmmf`
12
+ * @param options - Subset of AdminOptions that affect visibility
13
+ */
14
+ export declare function parseDmmf(dmmf: DmmfDocument, options: Pick<AdminOptions, 'showAuthModels' | 'hiddenModels'>): AdminModelMeta[];
15
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/dmmf/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAI7E;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,gBAAgB,GAAG,cAAc,CAAC,GAC7D,cAAc,EAAE,CAqClB"}
@@ -0,0 +1,54 @@
1
+ import { DEFAULT_HIDDEN_MODELS } from '../constants.js';
2
+ import { mapField, getDisplayField, getListFields, getFormFields } from './fields.js';
3
+ /**
4
+ * Converts a model name to a plural display name.
5
+ * Handles the most common English pluralisation rules.
6
+ */
7
+ export function toDisplayName(name) {
8
+ if (name.endsWith('s'))
9
+ return name;
10
+ if (name.endsWith('y') && !/[aeiou]y$/i.test(name)) {
11
+ return name.slice(0, -1) + 'ies';
12
+ }
13
+ return name + 's';
14
+ }
15
+ /**
16
+ * Parses the Prisma DMMF document into an array of AdminModelMeta objects,
17
+ * applying visibility rules (hidden models) in the process.
18
+ *
19
+ * @param dmmf - The DMMF document from `Prisma.dmmf`
20
+ * @param options - Subset of AdminOptions that affect visibility
21
+ */
22
+ export function parseDmmf(dmmf, options) {
23
+ const hidden = new Set([
24
+ ...(options.showAuthModels === true ? [] : DEFAULT_HIDDEN_MODELS),
25
+ ...(options.hiddenModels ?? []),
26
+ ]);
27
+ return dmmf.datamodel.models
28
+ .filter((model) => !hidden.has(model.name))
29
+ .map((model) => {
30
+ // TEMP DEBUG — remove before commit
31
+ if (model.name === 'Post') {
32
+ console.log('[admin debug] Post first field raw:');
33
+ console.log(JSON.stringify(model.fields[0], null, 2));
34
+ }
35
+ const fields = model.fields.map((f) => mapField(f, dmmf.datamodel.enums));
36
+ const idField = fields.find((f) => f.isId) ?? fields[0];
37
+ if (idField === undefined) {
38
+ throw new Error(`Admin: model "${model.name}" has no fields — cannot build admin meta.`);
39
+ }
40
+ const idType = idField.prismaType === 'Int' || idField.prismaType === 'Float' ? 'number' : 'string';
41
+ return {
42
+ name: model.name,
43
+ urlSlug: model.name.toLowerCase(),
44
+ displayName: toDisplayName(model.name),
45
+ idField: idField.name,
46
+ idType,
47
+ displayField: getDisplayField(fields),
48
+ fields,
49
+ listFields: getListFields(fields),
50
+ formFields: getFormFields(fields),
51
+ };
52
+ });
53
+ }
54
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/dmmf/parser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAErF;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAA;IAClC,CAAC;IACD,OAAO,IAAI,GAAG,GAAG,CAAA;AACnB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CACvB,IAAkB,EAClB,OAA8D;IAE9D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAS;QAC7B,GAAG,CAAC,OAAO,CAAC,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,qBAAqB,CAAC;QACjE,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;KAChC,CAAC,CAAA;IAEF,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM;SACzB,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SAC1C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,oCAAoC;QACpC,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAA;YAClD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;QACvD,CAAC;QAED,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;QAEzE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAA;QACvD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,iBAAiB,KAAK,CAAC,IAAI,4CAA4C,CAAC,CAAA;QAC1F,CAAC;QAED,MAAM,MAAM,GACV,OAAO,CAAC,UAAU,KAAK,KAAK,IAAI,OAAO,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAA;QAEtF,OAAO;YACL,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE;YACjC,WAAW,EAAE,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC;YACtC,OAAO,EAAE,OAAO,CAAC,IAAI;YACrB,MAAM;YACN,YAAY,EAAE,eAAe,CAAC,MAAM,CAAC;YACrC,MAAM;YACN,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC;YACjC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC;SAClC,CAAA;IACH,CAAC,CAAC,CAAA;AACN,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { mountAdmin } from './mount.js';
2
+ export { scaffoldAdmin } from './scaffold/index.js';
3
+ export type { AdminOptions, AdminModelMeta, AdminFieldMeta, AdminFieldKind, AdminPaginationMeta, AdminAuthLike, LoggerLike, ScaffoldOptions, ScaffoldResult, } from './types.js';
4
+ export { DEFAULT_HIDDEN_MODELS, DEFAULT_PREFIX, DEFAULT_PER_PAGE, MAX_RELATED_OPTIONS } from './constants.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,YAAY,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,UAAU,EACV,eAAe,EACf,cAAc,GACf,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { mountAdmin } from './mount.js';
2
+ export { scaffoldAdmin } from './scaffold/index.js';
3
+ export { DEFAULT_HIDDEN_MODELS, DEFAULT_PREFIX, DEFAULT_PER_PAGE, MAX_RELATED_OPTIONS } from './constants.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAcnD,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,8 @@
1
+ import type { RequestHandler } from 'express';
2
+ import type { AdminAuthLike } from '../types.js';
3
+ /**
4
+ * Returns an Express middleware that validates a Better Auth session and checks
5
+ * whether the authenticated user has the 'admin' role.
6
+ */
7
+ export declare function createAdminAuthMiddleware(auth: AdminAuthLike): RequestHandler;
8
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAmC,MAAM,SAAS,CAAA;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,aAAa,GAAG,cAAc,CAkB7E"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Returns an Express middleware that validates a Better Auth session and checks
3
+ * whether the authenticated user has the 'admin' role.
4
+ */
5
+ export function createAdminAuthMiddleware(auth) {
6
+ return async (req, res, next) => {
7
+ const session = await auth.api.getSession({
8
+ headers: req.headers,
9
+ });
10
+ if (session === null || session === undefined) {
11
+ res.status(403).json({ error: 'Forbidden: no active session' });
12
+ return;
13
+ }
14
+ if (session.user.role !== 'admin') {
15
+ res.status(403).json({ error: 'Forbidden: not an admin' });
16
+ return;
17
+ }
18
+ next();
19
+ };
20
+ }
21
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAmB;IAC3D,OAAO,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAiB,EAAE;QAC9E,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;YACxC,OAAO,EAAE,GAAG,CAAC,OAA6B;SAC3C,CAAC,CAAA;QAEF,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC9C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC,CAAA;YAC/D,OAAM;QACR,CAAC;QAED,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAClC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAA;YAC1D,OAAM;QACR,CAAC;QAED,IAAI,EAAE,CAAA;IACR,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Application } from 'express';
2
+ import type { AdminOptions, PrismaClientLike } from './types.js';
3
+ export declare function mountAdmin(app: Application, prisma: PrismaClientLike, options: AdminOptions): void;
4
+ //# sourceMappingURL=mount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAOhE,wBAAgB,UAAU,CACxB,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,YAAY,GACpB,IAAI,CAkBN"}