@cfast/admin 0.0.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Schmidt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,233 @@
1
+ # @cfast/admin
2
+
3
+ **A complete admin panel generated from your Drizzle schema. With role management and user impersonation.**
4
+
5
+ `@cfast/admin` gives you a production-ready admin UI derived from your database schema. It's not a generic CRUD generator. It understands your permission system, your auth setup, and your data relationships. Every table gets a list view, detail view, create form, and edit form. Users get a role management panel. Admins get impersonation.
6
+
7
+ You add one route to your React Router app, and you have an admin panel.
8
+
9
+ ## Why This Exists
10
+
11
+ Building an admin panel is the same work every time: list pages, detail pages, create/edit forms, user management, role assignment. The structure is always the same — only the schema changes.
12
+
13
+ `@cfast/admin` automates the schema → configuration step. It reads your Drizzle tables, infers column types, and generates the configuration for `@cfast/ui` components. The actual rendering is delegated entirely to `@cfast/ui` — admin doesn't have its own component library.
14
+
15
+ This means:
16
+ - Apps that don't use the admin panel still get `<ListView>`, `<DetailView>`, and `<DataTable>` from `@cfast/ui`
17
+ - Custom admin overrides use the same components as the rest of the app
18
+ - Admin stays thin and focused on auto-generation
19
+
20
+ ## Design Goals
21
+
22
+ - **One route, full admin.** Mount the admin at `/admin` and you're done. Every table, every relationship, every action.
23
+ - **Permission-aware by default.** The admin panel uses `@cfast/db` under the hood. Admins see everything. Moderators see what moderators see. The admin UI doesn't bypass your permission system — it uses it.
24
+ - **User management built in.** View users, assign roles, revoke roles, impersonate users. Integrated with `@cfast/auth`.
25
+ - **Customizable, not locked in.** Override any view, any field, any action. But the default is good enough to ship.
26
+ - **UI delegated to `@cfast/ui`.** Admin generates configuration. `@cfast/ui/joy` renders it.
27
+
28
+ ## API
29
+
30
+ ### Minimal Setup
31
+
32
+ ```typescript
33
+ // app/routes/admin.tsx
34
+ import { createAdmin } from "@cfast/admin";
35
+ import type { AdminAuthConfig } from "@cfast/admin";
36
+ import * as schema from "~/schema";
37
+
38
+ // Auth adapter — bridges your app's auth to admin's interface
39
+ const auth: AdminAuthConfig = {
40
+ async requireUser(request) {
41
+ // Return { user: AdminUser, grants: unknown[] }
42
+ const session = await getSession(request);
43
+ return { user: session.user, grants: session.grants };
44
+ },
45
+ hasRole: (user, role) => user.roles.includes(role),
46
+ getRoles: (userId) => authInstance.getRoles(userId),
47
+ setRole: (userId, role) => authInstance.setRole(userId, role),
48
+ removeRole: (userId, role) => authInstance.removeRole(userId, role),
49
+ setRoles: (userId, roles) => authInstance.setRoles(userId, roles),
50
+ impersonate: (adminId, targetId, request) => { /* ... */ },
51
+ stopImpersonation: (request) => { /* ... */ },
52
+ };
53
+
54
+ // DB factory — called per-request with grants and user context
55
+ function db(grants: unknown[], user: { id: string } | null) {
56
+ return createDb({ d1: env.DB, schema, grants, user });
57
+ }
58
+
59
+ const admin = createAdmin({
60
+ db,
61
+ auth,
62
+ schema,
63
+ requiredRole: "admin", // Role required to access admin (default: "admin")
64
+ });
65
+
66
+ // React Router route:
67
+ export const loader = admin.loader;
68
+ export const action = admin.action;
69
+ export default admin.Component;
70
+ ```
71
+
72
+ ### Server/Client Splitting
73
+
74
+ For React Router apps where server code must not leak into client bundles, use the individual factories:
75
+
76
+ ```typescript
77
+ // app/admin.server.ts — server only
78
+ import { createAdminLoader, createAdminAction, introspectSchema } from "@cfast/admin";
79
+
80
+ const tableMetas = introspectSchema(schema);
81
+ export const adminLoader = createAdminLoader(config, tableMetas);
82
+ export const adminAction = createAdminAction(config, tableMetas);
83
+ ```
84
+
85
+ ```typescript
86
+ // app/routes/admin.tsx — safe for client bundle
87
+ import { createAdminComponent, introspectSchema } from "@cfast/admin";
88
+ import { adminLoader, adminAction } from "~/admin.server";
89
+
90
+ const tableMetas = introspectSchema(schema);
91
+ const AdminComponent = createAdminComponent(tableMetas);
92
+
93
+ export const loader = adminLoader;
94
+ export const action = adminAction;
95
+ export default AdminComponent;
96
+ ```
97
+
98
+ ### Table Configuration
99
+
100
+ Customize how tables appear in the admin:
101
+
102
+ ```typescript
103
+ createAdmin({
104
+ db,
105
+ auth,
106
+ schema,
107
+ tables: {
108
+ posts: {
109
+ label: "Blog Posts",
110
+ listColumns: ["title", "author", "published", "createdAt"],
111
+ searchable: ["title", "content"],
112
+ defaultSort: { column: "createdAt", direction: "desc" },
113
+ exclude: false, // Set true to hide a table from admin
114
+ fields: {
115
+ content: { component: RichTextEditor },
116
+ },
117
+ },
118
+ // Tables not listed here use sensible defaults
119
+ // Auth-internal tables (session, account, verification, passkey) are auto-excluded
120
+ },
121
+ });
122
+ ```
123
+
124
+ ### User Management
125
+
126
+ Built-in views for managing users and roles:
127
+
128
+ ```typescript
129
+ createAdmin({
130
+ // ...
131
+ users: {
132
+ // Which roles can be assigned through the admin UI
133
+ // (respects auth.roleGrants for who can assign what)
134
+ assignableRoles: ["user", "editor", "moderator", "admin"],
135
+ },
136
+ });
137
+ ```
138
+
139
+ The admin automatically provides:
140
+ - **User list** with search and filters (via `@cfast/ui`'s `<ListView>`)
141
+ - **User detail** page with profile info and activity (via `@cfast/ui`'s `<DetailView>`)
142
+ - **Role assignment** panel (respects `roleGrants` from `@cfast/auth`)
143
+ - **Impersonation** button (for authorized roles) - starts an impersonation session via `@cfast/auth`
144
+
145
+ ### Impersonation UX
146
+
147
+ When an admin impersonates a user:
148
+
149
+ 1. The admin panel shows an impersonation banner with a "Stop Impersonation" button
150
+ 2. The rest of the app behaves as that user (same session, same permissions)
151
+ 3. Impersonation start/stop is handled by the `auth.impersonate` and `auth.stopImpersonation` callbacks you provide
152
+ 4. Audit logging is the responsibility of your auth adapter (see the example in Minimal Setup)
153
+
154
+ ### Custom Actions
155
+
156
+ Add table-level or row-level actions:
157
+
158
+ ```typescript
159
+ createAdmin({
160
+ // ...
161
+ tables: {
162
+ posts: {
163
+ actions: {
164
+ row: [
165
+ {
166
+ label: "Publish",
167
+ action: async (id: string, formData: FormData) => {
168
+ // Custom logic — called with the record ID and form data
169
+ },
170
+ confirm: "Are you sure you want to publish?", // Optional confirmation dialog
171
+ variant: "default", // "default" | "danger" — controls button styling
172
+ },
173
+ ],
174
+ table: [
175
+ {
176
+ label: "Export CSV",
177
+ handler: async (selectedIds: string[]) => {
178
+ // Called with an array of selected record IDs
179
+ },
180
+ },
181
+ ],
182
+ },
183
+ },
184
+ },
185
+ });
186
+ ```
187
+
188
+ ### Dashboard
189
+
190
+ The admin index page shows an overview dashboard:
191
+
192
+ ```typescript
193
+ createAdmin({
194
+ // ...
195
+ dashboard: {
196
+ widgets: [
197
+ { type: "count", table: "users", label: "Total Users" },
198
+ { type: "count", table: "posts", label: "Published Posts", where: { published: true } },
199
+ { type: "recent", table: "posts", limit: 5, label: "Recent Posts" },
200
+ ],
201
+ },
202
+ });
203
+ ```
204
+
205
+ ## How It Works
206
+
207
+ `@cfast/admin` is a thin layer that does two things:
208
+
209
+ 1. **Schema introspection** — reads your Drizzle schema to generate configuration: which columns to show, which fields to use in forms, which relations to resolve, which actions to offer
210
+ 2. **Configuration → UI components** — passes that configuration to `@cfast/ui` components for rendering
211
+
212
+ The rendering stack:
213
+
214
+ | Admin feature | Rendered with |
215
+ |---|---|
216
+ | Create/edit forms | `@cfast/forms`' `<AutoForm>` |
217
+ | User role display | `@cfast/ui`'s `<RoleBadge>` |
218
+ | User avatars | `@cfast/ui`'s `<AvatarWithInitials>` |
219
+ | Confirm dialogs | `@cfast/ui`'s `useConfirm` |
220
+ | List, detail, dashboard views | Built-in admin components (MUI Joy) |
221
+ | Navigation sidebar | Built-in admin components (MUI Joy) |
222
+
223
+ ## Integration
224
+
225
+ - **`@cfast/ui`** — All rendering. Admin generates configuration, UI renders pixels.
226
+ - **`@cfast/db`** — All data access. Every CRUD operation goes through permission-checked Operations via `.run()`.
227
+ - **`@cfast/auth`** — User management, role assignment, and impersonation.
228
+ - **`@cfast/forms`** — Create/edit forms via `<AutoForm>`.
229
+ - **`@cfast/actions`** — Custom row and table actions, permission-aware.
230
+ - **`@cfast/permissions`** — The admin respects the permission system. An editor role in the admin sees what editors see.
231
+ - **`@cfast/pagination`** — List views paginate via `@cfast/pagination` hooks.
232
+
233
+ The admin is not a separate app. It's a React Router route that uses the same database, same permissions, same auth as the rest of your application.