@paneljs/paneljs 0.1.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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/dist/adapter.d.ts +24 -0
  4. package/dist/adapter.js +2 -0
  5. package/dist/adapter.js.map +1 -0
  6. package/dist/audit.d.ts +5 -0
  7. package/dist/audit.js +11 -0
  8. package/dist/audit.js.map +1 -0
  9. package/dist/authMode.d.ts +2 -0
  10. package/dist/authMode.js +2 -0
  11. package/dist/authMode.js.map +1 -0
  12. package/dist/defaultActions.d.ts +5 -0
  13. package/dist/defaultActions.js +6 -0
  14. package/dist/defaultActions.js.map +1 -0
  15. package/dist/errors.d.ts +18 -0
  16. package/dist/errors.js +36 -0
  17. package/dist/errors.js.map +1 -0
  18. package/dist/ids.d.ts +3 -0
  19. package/dist/ids.js +14 -0
  20. package/dist/ids.js.map +1 -0
  21. package/dist/index.d.ts +27 -0
  22. package/dist/index.js +40 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/listQuery.d.ts +19 -0
  25. package/dist/listQuery.js +106 -0
  26. package/dist/listQuery.js.map +1 -0
  27. package/dist/passwords.d.ts +4 -0
  28. package/dist/passwords.js +57 -0
  29. package/dist/passwords.js.map +1 -0
  30. package/dist/permissions.d.ts +15 -0
  31. package/dist/permissions.js +36 -0
  32. package/dist/permissions.js.map +1 -0
  33. package/dist/recordSelection.d.ts +13 -0
  34. package/dist/recordSelection.js +65 -0
  35. package/dist/recordSelection.js.map +1 -0
  36. package/dist/registry.d.ts +156 -0
  37. package/dist/registry.js +388 -0
  38. package/dist/registry.js.map +1 -0
  39. package/dist/schema.d.ts +8 -0
  40. package/dist/schema.js +59 -0
  41. package/dist/schema.js.map +1 -0
  42. package/dist/scope.d.ts +31 -0
  43. package/dist/scope.js +86 -0
  44. package/dist/scope.js.map +1 -0
  45. package/dist/types.d.ts +189 -0
  46. package/dist/types.js +2 -0
  47. package/dist/types.js.map +1 -0
  48. package/dist/uiPath.d.ts +2 -0
  49. package/dist/uiPath.js +7 -0
  50. package/dist/uiPath.js.map +1 -0
  51. package/dist/validation.d.ts +29 -0
  52. package/dist/validation.js +147 -0
  53. package/dist/validation.js.map +1 -0
  54. package/package.json +54 -0
  55. package/ui/dist/assets/index-ChtweaBk.js +11 -0
  56. package/ui/dist/assets/index-eXfQCDXx.css +1 -0
  57. package/ui/dist/fonts/dm-mono-400.woff2 +0 -0
  58. package/ui/dist/fonts/dm-mono-500.woff2 +0 -0
  59. package/ui/dist/fonts/dm-sans-400.woff2 +0 -0
  60. package/ui/dist/fonts/dm-sans-500.woff2 +0 -0
  61. package/ui/dist/fonts/dm-sans-600.woff2 +0 -0
  62. package/ui/dist/fonts/dm-sans-700.woff2 +0 -0
  63. package/ui/dist/index.html +14 -0
@@ -0,0 +1,65 @@
1
+ import { hasModelPermission } from "./permissions.js";
2
+ import { buildScopedRecordWhere, resolveScope } from "./scope.js";
3
+ import { parseRecordId } from "./ids.js";
4
+ import { isFieldVisible, isSensitiveFieldName, RequestValidationError } from "./validation.js";
5
+ import { PermissionDeniedError } from "./errors.js";
6
+ /** Build the safe select used by detail and edit endpoints. */
7
+ export function buildRecordSelect(meta, model) {
8
+ const select = Object.fromEntries(meta.fields.filter((field) => field.type !== "relation" && isFieldVisible(field, model.raw)).map((field) => [field.name, true]));
9
+ for (const fieldName of model.resolved.listDisplay) {
10
+ const field = meta.fields.find((candidate) => candidate.name === fieldName);
11
+ if (!field?.relation || !isFieldVisible(field, model.raw))
12
+ continue;
13
+ if (field.relation.kind !== "belongsTo" && field.relation.kind !== "hasOne")
14
+ continue;
15
+ if (isSensitiveFieldName(field.relation.displayField))
16
+ continue;
17
+ select[field.name] = { select: { [field.relation.displayField]: true } };
18
+ }
19
+ return select;
20
+ }
21
+ /** Only return columns the list is configured to display, plus the primary key. */
22
+ export function buildListRecordSelect(meta, model) {
23
+ const allowed = new Set([meta.idField, ...model.resolved.listDisplay]);
24
+ const select = Object.fromEntries(meta.fields.filter((field) => field.type !== "relation" && allowed.has(field.name) && isFieldVisible(field, model.raw)).map((field) => [field.name, true]));
25
+ select[meta.idField] = true;
26
+ for (const fieldName of model.resolved.listDisplay) {
27
+ const field = meta.fields.find((candidate) => candidate.name === fieldName);
28
+ if (!field?.relation || !isFieldVisible(field, model.raw))
29
+ continue;
30
+ if (field.relation.kind !== "belongsTo" && field.relation.kind !== "hasOne")
31
+ continue;
32
+ if (!isSensitiveFieldName(field.relation.displayField))
33
+ select[field.name] = { select: { [field.relation.displayField]: true } };
34
+ }
35
+ return select;
36
+ }
37
+ /** Verify submitted belongs-to IDs are visible under the related model's own access rules. */
38
+ export async function assertSelectedRelationsAreVisible(data, model, models, adapter, adminUser) {
39
+ const modelsByName = new Map([...models.values()].map((candidate) => [candidate.meta.name, candidate]));
40
+ for (const relationField of model.meta.fields) {
41
+ const relation = relationField.relation;
42
+ if (relationField.type !== "relation" || relation?.kind !== "belongsTo" || relation.foreignKeyFields.length !== 1)
43
+ continue;
44
+ const foreignKeyField = relation.foreignKeyFields[0];
45
+ if (!foreignKeyField || !(foreignKeyField in data))
46
+ continue;
47
+ const selectedId = data[foreignKeyField];
48
+ if (selectedId === null)
49
+ continue;
50
+ const relatedModel = modelsByName.get(relation.model);
51
+ if (!relatedModel) {
52
+ throw new RequestValidationError(`Relation "${relationField.name}" cannot be changed because related model "${relation.model}" is not registered.`);
53
+ }
54
+ if (!hasModelPermission(adminUser, relatedModel.resolved.permissions, "list"))
55
+ throw new PermissionDeniedError();
56
+ const relatedScope = await resolveScope(relatedModel.raw, adminUser);
57
+ const relatedRecord = await adapter.resource(relatedModel.meta).findFirst({
58
+ where: buildScopedRecordWhere(relatedScope, relatedModel.meta.idField, parseRecordId(relatedModel.meta, String(selectedId))),
59
+ select: { [relatedModel.meta.idField]: true },
60
+ });
61
+ if (!relatedRecord)
62
+ throw new RequestValidationError(`The selected ${relation.model} record is unavailable.`);
63
+ }
64
+ }
65
+ //# sourceMappingURL=recordSelection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordSelection.js","sourceRoot":"","sources":["../src/recordSelection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,156 @@
1
+ import type { AdminModelMeta, ModelConfig, ModelPermissions } from "./types.js";
2
+ /**
3
+ * A fully-resolved model configuration — every property is guaranteed
4
+ * to have a value (no undefined/optional). This is what the rest of the
5
+ * system uses internally (routerFactory, schemaEndpoint, etc.).
6
+ *
7
+ * It's distinct from ModelConfig (which is all-optional) so TypeScript
8
+ * can enforce that consumers don't need to null-check these fields.
9
+ */
10
+ export interface ResolvedModelConfig {
11
+ listDisplay: string[];
12
+ listFilter: string[];
13
+ searchFields: string[];
14
+ defaultSort: {
15
+ field: string;
16
+ direction: "asc" | "desc";
17
+ };
18
+ perPage: number;
19
+ permissions: ModelPermissions;
20
+ }
21
+ /**
22
+ * A registered model entry — the full picture of a model in the admin.
23
+ * Combines the DMMF-derived AdminModelMeta with the fully resolved config.
24
+ *
25
+ * The raw user-supplied config is kept as well for cases where the
26
+ * caller needs access to lifecycle hooks, scope functions, etc.
27
+ * (Those can't go into ResolvedModelConfig because they hold functions,
28
+ * and functions aren't serializable to JSON for the schema endpoint.)
29
+ */
30
+ export interface FullRegisteredModel {
31
+ /** DMMF-derived metadata — auto-generated from schema */
32
+ meta: AdminModelMeta;
33
+ /**
34
+ * Fully resolved config — all defaults filled in.
35
+ * Safe to access any property without null-checking.
36
+ * Used by: routerFactory, schemaEndpoint, filterParser, includeBuilder.
37
+ */
38
+ resolved: ResolvedModelConfig;
39
+ /**
40
+ * The original developer-supplied config — kept for lifecycle hooks,
41
+ * scope functions, actions, and field overrides that can't be resolved
42
+ * at registration time.
43
+ * Used by: routerFactory (hooks, scope), auth/permissions middleware.
44
+ */
45
+ raw: ModelConfig;
46
+ }
47
+ /**
48
+ * The AdminRegistry holds all registered models and provides the interface
49
+ * for the rest of the system to look them up.
50
+ *
51
+ * Lifecycle:
52
+ * 1. createAdmin() creates a registry instance.
53
+ * 2. admin.register("ModelName", config?) adds each model.
54
+ * 3. admin.mount(app) triggers initialization — introspects the schema,
55
+ * validates all registrations, resolves configs.
56
+ * 4. After init, the routerFactory and schemaEndpoint read from the registry.
57
+ *
58
+ * The registry is NOT async at registration time. Developers call
59
+ * admin.register() synchronously. The async getDMMF work happens during
60
+ * admin.mount() / initialize(), not at register() time. This keeps the
61
+ * developer API synchronous and clean.
62
+ */
63
+ export declare class AdminRegistry {
64
+ /**
65
+ * Raw pending registrations: model name → user config.
66
+ * These are stored at admin.register() time but not yet resolved.
67
+ * Resolution happens during initialize() when we have DMMF data.
68
+ */
69
+ private pendingRegistrations;
70
+ /**
71
+ * Fully resolved registered models, populated by initialize().
72
+ * Map key is the model name exactly as defined in Prisma schema ("User", "Post").
73
+ */
74
+ private models;
75
+ /**
76
+ * Whether initialize() has been called successfully.
77
+ * Guards against using the registry before it's ready.
78
+ */
79
+ private initialized;
80
+ /**
81
+ * Register a model with the admin.
82
+ *
83
+ * This is the public API that developers call:
84
+ * admin.register("User")
85
+ * admin.register("Post", { listDisplay: ["title", "author", "published"] })
86
+ *
87
+ * Registration is synchronous and lightweight — it just stores the intent.
88
+ * The actual introspection and config resolution happens lazily in initialize().
89
+ *
90
+ * Important: You can call register() before the Prisma schema has been
91
+ * generated. The validation only happens at mount time.
92
+ *
93
+ * @param modelName - The model name exactly as it appears in schema.prisma (PascalCase)
94
+ * @param config - Optional customization config. Every field is optional.
95
+ */
96
+ register(modelName: string, config?: ModelConfig): void;
97
+ /**
98
+ * Initialize the registry.
99
+ *
100
+ * This is called internally by admin.mount(app). Developers never call this directly.
101
+ *
102
+ * What happens here:
103
+ * 1. Run the introspector → get AdminModelMeta for every model in the schema.
104
+ * 2. For each pending registration, validate the model name exists in the schema.
105
+ * 3. If the developer provided field overrides (config.fields), validate that
106
+ * every field name they referenced actually exists on the model.
107
+ * 4. Resolve the config — fill in all defaults.
108
+ * 5. Store the fully resolved FullRegisteredModel.
109
+ *
110
+ * After this, the registry is "sealed" — no more register() calls are allowed.
111
+ *
112
+ * @param allModelMeta - Model metadata from the host data adapter.
113
+ */
114
+ initialize(allModelMeta: Map<string, AdminModelMeta>): Promise<void>;
115
+ /**
116
+ * Assert that initialize() has been called.
117
+ * Every read method calls this to give clear errors if accessed too early.
118
+ */
119
+ private assertInitialized;
120
+ /**
121
+ * Get a single registered model by its Prisma model name.
122
+ *
123
+ * Returns undefined if the model was not registered.
124
+ * Consumers should handle undefined — not every model in the schema
125
+ * is necessarily registered in the admin.
126
+ *
127
+ * @param modelName - PascalCase model name, e.g. "User", "Post"
128
+ */
129
+ get(modelName: string): FullRegisteredModel | undefined;
130
+ /**
131
+ * Get a registered model by its plural URL name.
132
+ * e.g. "users" → the User FullRegisteredModel
133
+ *
134
+ * Used by the router to resolve "GET /admin/api/users" → User model.
135
+ *
136
+ * @param pluralName - e.g. "users", "posts", "categories"
137
+ */
138
+ getByPluralName(pluralName: string): FullRegisteredModel | undefined;
139
+ /**
140
+ * Get all registered models, in registration order.
141
+ *
142
+ * Used by:
143
+ * - GET /admin/api/schema → returns all models to the UI
144
+ * - The admin sidebar → lists all models for navigation
145
+ */
146
+ getAll(): FullRegisteredModel[];
147
+ /**
148
+ * Returns the number of registered models.
149
+ * Useful for health checks and logging.
150
+ */
151
+ get size(): number;
152
+ /**
153
+ * Whether a model name is currently registered.
154
+ */
155
+ has(modelName: string): boolean;
156
+ }
@@ -0,0 +1,388 @@
1
+ import { DELETE_SELECTED_ACTION } from "./defaultActions.js";
2
+ // ============================================================
3
+ // DEFAULT RESOLUTION
4
+ // ============================================================
5
+ /**
6
+ * How many non-relation, non-readonly scalar fields to show in
7
+ * list view when the developer hasn't specified listDisplay.
8
+ */
9
+ const DEFAULT_LIST_DISPLAY_LIMIT = 6;
10
+ /**
11
+ * Default number of records per page.
12
+ */
13
+ const DEFAULT_PER_PAGE = 50;
14
+ /**
15
+ * Given raw AdminModelMeta (from DMMF) and a developer-supplied ModelConfig
16
+ * (which may be empty {}), produce a complete, fully-resolved config where
17
+ * every property has a value.
18
+ *
19
+ * This is where "zero config" is implemented. A developer calling
20
+ * admin.register("User") with no options still gets a fully working admin
21
+ * because this function fills in every missing piece intelligently.
22
+ *
23
+ * ── listDisplay ───────────────────────────────────────────────────────────
24
+ * Default: the first N scalar fields that are not:
25
+ * - relation fields (they need a sub-query, we don't include them in lists by default)
26
+ * - the id field (it's usually a UUID, not useful in a list table)
27
+ * - but we DO include the displayField always, even if it would be beyond the limit
28
+ *
29
+ * We put the displayField first so the list table always leads with the
30
+ * human-readable identifier for the record.
31
+ *
32
+ * ── listFilter ────────────────────────────────────────────────────────────
33
+ * Default: no filters. Developers opt in with listFilter so list pages only
34
+ * show the filters that are useful for that model.
35
+ *
36
+ * ── searchFields ──────────────────────────────────────────────────────────
37
+ * Default: every field the introspector marked as isSearchable.
38
+ * These are non-id, non-FK string fields.
39
+ *
40
+ * ── defaultSort ───────────────────────────────────────────────────────────
41
+ * Default: sort by createdAt desc (most recent first) if the model has
42
+ * a createdAt timestamp. Otherwise sort by the id field descending.
43
+ * Developers commonly want newest records first in a list view.
44
+ *
45
+ * ── perPage ───────────────────────────────────────────────────────────────
46
+ * Default: 50. The developer can override this per model.
47
+ *
48
+ * ── permissions ───────────────────────────────────────────────────────────
49
+ * Default: authenticated admins may list and view. Create, update, delete,
50
+ * and custom actions require an explicit role allowlist.
51
+ */
52
+ function resolveConfig(meta, userConfig) {
53
+ // ── listDisplay ────────────────────────────────────────────
54
+ let listDisplay;
55
+ if (userConfig.listDisplay && userConfig.listDisplay.length > 0) {
56
+ // Developer explicitly specified which columns to show
57
+ listDisplay = userConfig.listDisplay;
58
+ }
59
+ else {
60
+ // Auto-generate: pick scalar, non-id, non-relation fields up to the limit.
61
+ // We exclude the id field from the list because it's a UUID and clutters the table.
62
+ // We exclude relation fields because they require extra includes and joins.
63
+ const candidates = meta.fields
64
+ .filter((f) => f.type !== "relation" && // no relation fields in list
65
+ !f.isId && // no id field
66
+ !f.isReadOnly)
67
+ .map((f) => f.name);
68
+ // Start with the displayField so the first column is always the human label
69
+ const result = [];
70
+ if (!result.includes(meta.displayField)) {
71
+ result.push(meta.displayField);
72
+ }
73
+ // Add up to the limit from the candidates
74
+ for (const name of candidates) {
75
+ if (result.length >= DEFAULT_LIST_DISPLAY_LIMIT)
76
+ break;
77
+ if (!result.includes(name)) {
78
+ result.push(name);
79
+ }
80
+ }
81
+ // Always add createdAt at the end if it exists and we have room
82
+ if (meta.timestamps.createdAt && !result.includes(meta.timestamps.createdAt)) {
83
+ result.push(meta.timestamps.createdAt);
84
+ }
85
+ listDisplay = result;
86
+ }
87
+ // ── listFilter ─────────────────────────────────────────────
88
+ const listFilter = userConfig.listFilter ?? [];
89
+ // ── searchFields ───────────────────────────────────────────
90
+ const searchFields = userConfig.searchFields ?? meta.searchableFields;
91
+ // ── defaultSort ────────────────────────────────────────────
92
+ let defaultSort;
93
+ if (userConfig.defaultSort) {
94
+ defaultSort = userConfig.defaultSort;
95
+ }
96
+ else if (meta.timestamps.createdAt) {
97
+ // Most useful default: newest records first
98
+ defaultSort = { field: meta.timestamps.createdAt, direction: "desc" };
99
+ }
100
+ else {
101
+ defaultSort = { field: meta.idField, direction: "desc" };
102
+ }
103
+ // ── perPage ────────────────────────────────────────────────
104
+ const perPage = userConfig.perPage ?? DEFAULT_PER_PAGE;
105
+ // ── permissions ────────────────────────────────────────────
106
+ const permissions = userConfig.permissions ?? {};
107
+ return {
108
+ listDisplay,
109
+ listFilter,
110
+ searchFields,
111
+ defaultSort,
112
+ perPage,
113
+ permissions,
114
+ };
115
+ }
116
+ // ============================================================
117
+ // REGISTRY CLASS
118
+ // ============================================================
119
+ /**
120
+ * The AdminRegistry holds all registered models and provides the interface
121
+ * for the rest of the system to look them up.
122
+ *
123
+ * Lifecycle:
124
+ * 1. createAdmin() creates a registry instance.
125
+ * 2. admin.register("ModelName", config?) adds each model.
126
+ * 3. admin.mount(app) triggers initialization — introspects the schema,
127
+ * validates all registrations, resolves configs.
128
+ * 4. After init, the routerFactory and schemaEndpoint read from the registry.
129
+ *
130
+ * The registry is NOT async at registration time. Developers call
131
+ * admin.register() synchronously. The async getDMMF work happens during
132
+ * admin.mount() / initialize(), not at register() time. This keeps the
133
+ * developer API synchronous and clean.
134
+ */
135
+ export class AdminRegistry {
136
+ /**
137
+ * Raw pending registrations: model name → user config.
138
+ * These are stored at admin.register() time but not yet resolved.
139
+ * Resolution happens during initialize() when we have DMMF data.
140
+ */
141
+ pendingRegistrations = new Map();
142
+ /**
143
+ * Fully resolved registered models, populated by initialize().
144
+ * Map key is the model name exactly as defined in Prisma schema ("User", "Post").
145
+ */
146
+ models = new Map();
147
+ /**
148
+ * Whether initialize() has been called successfully.
149
+ * Guards against using the registry before it's ready.
150
+ */
151
+ initialized = false;
152
+ /**
153
+ * Register a model with the admin.
154
+ *
155
+ * This is the public API that developers call:
156
+ * admin.register("User")
157
+ * admin.register("Post", { listDisplay: ["title", "author", "published"] })
158
+ *
159
+ * Registration is synchronous and lightweight — it just stores the intent.
160
+ * The actual introspection and config resolution happens lazily in initialize().
161
+ *
162
+ * Important: You can call register() before the Prisma schema has been
163
+ * generated. The validation only happens at mount time.
164
+ *
165
+ * @param modelName - The model name exactly as it appears in schema.prisma (PascalCase)
166
+ * @param config - Optional customization config. Every field is optional.
167
+ */
168
+ register(modelName, config = {}) {
169
+ if (this.initialized) {
170
+ throw new Error(`[paneljs] Cannot call register("${modelName}") after admin.mount() has been called. ` + `All register() calls must happen before mounting the admin router.`);
171
+ }
172
+ if (this.pendingRegistrations.has(modelName)) {
173
+ console.warn(`[paneljs] Model "${modelName}" registered more than once. The second registration will overwrite the first.`);
174
+ }
175
+ this.pendingRegistrations.set(modelName, config);
176
+ }
177
+ /**
178
+ * Initialize the registry.
179
+ *
180
+ * This is called internally by admin.mount(app). Developers never call this directly.
181
+ *
182
+ * What happens here:
183
+ * 1. Run the introspector → get AdminModelMeta for every model in the schema.
184
+ * 2. For each pending registration, validate the model name exists in the schema.
185
+ * 3. If the developer provided field overrides (config.fields), validate that
186
+ * every field name they referenced actually exists on the model.
187
+ * 4. Resolve the config — fill in all defaults.
188
+ * 5. Store the fully resolved FullRegisteredModel.
189
+ *
190
+ * After this, the registry is "sealed" — no more register() calls are allowed.
191
+ *
192
+ * @param allModelMeta - Model metadata from the host data adapter.
193
+ */
194
+ async initialize(allModelMeta) {
195
+ if (this.initialized) {
196
+ throw new Error(`[paneljs] initialize() called more than once. This is a bug.`);
197
+ }
198
+ // ── Validate and resolve each pending registration ─────────
199
+ for (const [modelName, rawConfig] of this.pendingRegistrations) {
200
+ const meta = allModelMeta.get(modelName);
201
+ // Guard: model name must exist in the schema
202
+ if (!meta) {
203
+ const available = [...allModelMeta.keys()].sort().join(", ");
204
+ throw new Error(`[paneljs] admin.register("${modelName}") failed: ` + `no model named "${modelName}" found in the Prisma schema.\n` + `Available models: ${available}`);
205
+ }
206
+ // Guard: validate field override names
207
+ // If the developer writes config.fields: { nonExistentField: { exclude: true } },
208
+ // that's almost certainly a typo. Fail loudly instead of silently ignoring it.
209
+ if (rawConfig.fields) {
210
+ const modelFieldNames = new Set(meta.fields.map((f) => f.name));
211
+ for (const overrideName of Object.keys(rawConfig.fields)) {
212
+ if (!modelFieldNames.has(overrideName)) {
213
+ throw new Error(`[paneljs] admin.register("${modelName}") config.fields: ` +
214
+ `field "${overrideName}" does not exist on model "${modelName}".\n` +
215
+ `Available fields: ${[...modelFieldNames].sort().join(", ")}`);
216
+ }
217
+ }
218
+ }
219
+ // Guard: validate listDisplay field names
220
+ if (rawConfig.listDisplay) {
221
+ const modelFieldNames = new Set(meta.fields.map((f) => f.name));
222
+ for (const fieldName of rawConfig.listDisplay) {
223
+ if (!modelFieldNames.has(fieldName)) {
224
+ throw new Error(`[paneljs] admin.register("${modelName}") config.listDisplay: ` +
225
+ `field "${fieldName}" does not exist on model "${modelName}".\n` +
226
+ `Available fields: ${[...modelFieldNames].sort().join(", ")}`);
227
+ }
228
+ }
229
+ }
230
+ // Guard: validate searchFields names
231
+ if (rawConfig.searchFields) {
232
+ const scalarStringFields = new Set(meta.fields.filter((f) => f.type === "string" && !f.isId).map((f) => f.name));
233
+ for (const fieldName of rawConfig.searchFields) {
234
+ if (!scalarStringFields.has(fieldName)) {
235
+ throw new Error(`[paneljs] admin.register("${modelName}") config.searchFields: ` +
236
+ `field "${fieldName}" either does not exist or is not a String field on model "${modelName}". ` +
237
+ `Only String fields can be used as search fields.`);
238
+ }
239
+ }
240
+ }
241
+ // Guard: list filters must be fields the introspector marked as
242
+ // filterable (enum, boolean, date-time, or foreign-key scalar).
243
+ if (rawConfig.listFilter) {
244
+ const filterableFieldNames = new Set(meta.filterableFields);
245
+ for (const fieldName of rawConfig.listFilter) {
246
+ if (!filterableFieldNames.has(fieldName)) {
247
+ throw new Error(`[paneljs] admin.register("${modelName}") config.listFilter: ` +
248
+ `field "${fieldName}" cannot be used as a filter. ` +
249
+ `Available filter fields: ${[...filterableFieldNames].sort().join(", ") || "none"}.`);
250
+ }
251
+ }
252
+ }
253
+ if (rawConfig.perPage !== undefined && (!Number.isSafeInteger(rawConfig.perPage) || rawConfig.perPage < 1 || rawConfig.perPage > 200)) {
254
+ throw new Error(`[paneljs] admin.register("${modelName}") config.perPage must be an integer from 1 to 200.`);
255
+ }
256
+ if (rawConfig.defaultSort) {
257
+ const sortField = meta.fields.find((field) => field.name === rawConfig.defaultSort?.field);
258
+ if (!sortField || sortField.type === "relation")
259
+ throw new Error(`[paneljs] admin.register("${modelName}") config.defaultSort.field must be a scalar field on the model.`);
260
+ if (rawConfig.defaultSort.direction !== "asc" && rawConfig.defaultSort.direction !== "desc")
261
+ throw new Error(`[paneljs] admin.register("${modelName}") config.defaultSort.direction must be "asc" or "desc".`);
262
+ }
263
+ if (rawConfig.pluralName !== undefined && !/^[a-z][a-z0-9-]*$/i.test(rawConfig.pluralName)) {
264
+ throw new Error(`[paneljs] admin.register("${modelName}") config.pluralName must contain only letters, numbers, and hyphens.`);
265
+ }
266
+ if (process.env.NODE_ENV === "production" && rawConfig.permissions === undefined) {
267
+ throw new Error(`[paneljs] admin.register("${modelName}") requires explicit permissions in production.`);
268
+ }
269
+ // Guard: custom actions must have stable, URL-safe names and unique
270
+ // identifiers. Failing at mount avoids discovering configuration
271
+ // mistakes only when an administrator clicks an action in production.
272
+ if (rawConfig.actions) {
273
+ const actionNames = new Set();
274
+ for (const action of rawConfig.actions) {
275
+ if (action.name === DELETE_SELECTED_ACTION.name) {
276
+ throw new Error(`[paneljs] admin.register("${modelName}") action name "${action.name}" is reserved for the built-in delete action.`);
277
+ }
278
+ if (!/^[a-z][a-z0-9_]*$/.test(action.name)) {
279
+ throw new Error(`[paneljs] admin.register("${modelName}") action name "${action.name}" must use lowercase letters, numbers, and underscores and begin with a letter.`);
280
+ }
281
+ if (actionNames.has(action.name)) {
282
+ throw new Error(`[paneljs] admin.register("${modelName}") defines the action "${action.name}" more than once.`);
283
+ }
284
+ if (!action.label.trim()) {
285
+ throw new Error(`[paneljs] admin.register("${modelName}") action "${action.name}" must have a label.`);
286
+ }
287
+ if (typeof action.handler !== "function") {
288
+ throw new Error(`[paneljs] admin.register("${modelName}") action "${action.name}" must define a handler.`);
289
+ }
290
+ actionNames.add(action.name);
291
+ }
292
+ }
293
+ // ── Apply displayField / pluralName overrides from config ─
294
+ // The developer can override what the introspector auto-detected.
295
+ // We apply these overrides directly onto a copy of the meta here
296
+ // so downstream consumers only need to look at meta, not config.
297
+ const resolvedMeta = { ...meta };
298
+ if (rawConfig.displayField) {
299
+ const fieldExists = meta.fields.some((f) => f.name === rawConfig.displayField);
300
+ if (!fieldExists) {
301
+ throw new Error(`[paneljs] admin.register("${modelName}") config.displayField: ` + `field "${rawConfig.displayField}" does not exist on model "${modelName}".`);
302
+ }
303
+ resolvedMeta.displayField = rawConfig.displayField;
304
+ }
305
+ if (rawConfig.pluralName) {
306
+ resolvedMeta.pluralName = rawConfig.pluralName.toLowerCase();
307
+ }
308
+ // ── Resolve the config — fill in all defaults ─────────────
309
+ const resolved = resolveConfig(resolvedMeta, rawConfig);
310
+ if ([...this.models.values()].some((entry) => entry.meta.pluralName === resolvedMeta.pluralName)) {
311
+ throw new Error(`[paneljs] admin.register("${modelName}") duplicates the pluralName "${resolvedMeta.pluralName}" of another registered model.`);
312
+ }
313
+ // ── Store the fully resolved entry ─────────────────────────
314
+ this.models.set(modelName, {
315
+ meta: resolvedMeta,
316
+ resolved,
317
+ raw: rawConfig,
318
+ });
319
+ }
320
+ this.initialized = true;
321
+ }
322
+ // ============================================================
323
+ // READ API (used by routerFactory, schemaEndpoint, auth middleware)
324
+ // ============================================================
325
+ /**
326
+ * Assert that initialize() has been called.
327
+ * Every read method calls this to give clear errors if accessed too early.
328
+ */
329
+ assertInitialized() {
330
+ if (!this.initialized) {
331
+ throw new Error(`[paneljs] Registry accessed before admin.mount() was called. ` + `Make sure you call admin.mount(app) before handling any requests.`);
332
+ }
333
+ }
334
+ /**
335
+ * Get a single registered model by its Prisma model name.
336
+ *
337
+ * Returns undefined if the model was not registered.
338
+ * Consumers should handle undefined — not every model in the schema
339
+ * is necessarily registered in the admin.
340
+ *
341
+ * @param modelName - PascalCase model name, e.g. "User", "Post"
342
+ */
343
+ get(modelName) {
344
+ this.assertInitialized();
345
+ return this.models.get(modelName);
346
+ }
347
+ /**
348
+ * Get a registered model by its plural URL name.
349
+ * e.g. "users" → the User FullRegisteredModel
350
+ *
351
+ * Used by the router to resolve "GET /admin/api/users" → User model.
352
+ *
353
+ * @param pluralName - e.g. "users", "posts", "categories"
354
+ */
355
+ getByPluralName(pluralName) {
356
+ this.assertInitialized();
357
+ for (const entry of this.models.values()) {
358
+ if (entry.meta.pluralName === pluralName)
359
+ return entry;
360
+ }
361
+ return undefined;
362
+ }
363
+ /**
364
+ * Get all registered models, in registration order.
365
+ *
366
+ * Used by:
367
+ * - GET /admin/api/schema → returns all models to the UI
368
+ * - The admin sidebar → lists all models for navigation
369
+ */
370
+ getAll() {
371
+ this.assertInitialized();
372
+ return [...this.models.values()];
373
+ }
374
+ /**
375
+ * Returns the number of registered models.
376
+ * Useful for health checks and logging.
377
+ */
378
+ get size() {
379
+ return this.initialized ? this.models.size : this.pendingRegistrations.size;
380
+ }
381
+ /**
382
+ * Whether a model name is currently registered.
383
+ */
384
+ has(modelName) {
385
+ return this.initialized ? this.models.has(modelName) : this.pendingRegistrations.has(modelName);
386
+ }
387
+ }
388
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { AdminUser, SchemaResponse } from "./types.js";
2
+ import type { AdminRegistry } from "./registry.js";
3
+ export declare function buildSchemaResponse(registry: AdminRegistry, options: {
4
+ adminUser: AdminUser;
5
+ siteName: string;
6
+ basePath: string;
7
+ authMode: "built-in" | "external";
8
+ }): SchemaResponse;