@openenvx/admin 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.
- package/README.md +449 -0
- package/dist/client/index.d.ts +15 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +77 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/resources.d.ts +45 -0
- package/dist/client/resources.d.ts.map +1 -0
- package/dist/config/config.d.ts +3 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/core/introspector.d.ts +23 -0
- package/dist/core/introspector.d.ts.map +1 -0
- package/dist/core/types.d.ts +94 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +129 -0
- package/dist/index.js.map +1 -0
- package/dist/server/data-provider.d.ts +33 -0
- package/dist/server/data-provider.d.ts.map +1 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +5572 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/router.d.ts +18 -0
- package/dist/server/router.d.ts.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# @openenvx/admin
|
|
2
|
+
|
|
3
|
+
Zero-config admin panel for Drizzle ORM powered by Refine and shadcn/ui.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Zero Configuration**: Works out of the box with your Drizzle schema
|
|
8
|
+
- **Runtime Generated**: Admin UI updates automatically when schema changes
|
|
9
|
+
- **Full CRUD**: List, create, edit, and delete operations for all tables
|
|
10
|
+
- **Type Safe**: Full TypeScript support with Drizzle type inference
|
|
11
|
+
- **Customizable**: Override any part of the UI or behavior
|
|
12
|
+
- **Modern Stack**: Built on Refine, shadcn/ui, and Tailwind CSS
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Install @openenvx/admin and required peer dependencies
|
|
18
|
+
npm install @openenvx/admin @refinedev/core @refinedev/react-table @tanstack/react-table drizzle-orm
|
|
19
|
+
|
|
20
|
+
# Install Refine's shadcn/ui components
|
|
21
|
+
npx shadcn@latest add https://ui.refine.dev/r/views.json
|
|
22
|
+
npx shadcn@latest add https://ui.refine.dev/r/data-table.json
|
|
23
|
+
npx shadcn@latest add https://ui.refine.dev/r/layout/layout-01.json
|
|
24
|
+
npx shadcn@latest add https://ui.refine.dev/r/buttons.json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Create Data Provider (Server)
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// app/api/admin/[...resource]/route.ts
|
|
33
|
+
import { createDrizzleDataProvider } from '@openenvx/admin/server';
|
|
34
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
35
|
+
import { Pool } from 'pg';
|
|
36
|
+
import * as schema from '@/db/schema';
|
|
37
|
+
|
|
38
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
39
|
+
const db = drizzle(pool, { schema });
|
|
40
|
+
|
|
41
|
+
// Create data provider with resource mapping
|
|
42
|
+
const dataProvider = createDrizzleDataProvider({
|
|
43
|
+
db,
|
|
44
|
+
resources: {
|
|
45
|
+
users: 'users',
|
|
46
|
+
posts: 'posts',
|
|
47
|
+
// Map resource names to table names
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Export route handlers
|
|
52
|
+
export const { GET, POST, PUT, DELETE } = dataProvider;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. Set up Refine with Admin Resources (Client)
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// app/admin/layout.tsx
|
|
59
|
+
'use client';
|
|
60
|
+
|
|
61
|
+
import { Refine } from '@refinedev/core';
|
|
62
|
+
import routerProvider from '@refinedev/nextjs-router';
|
|
63
|
+
import dataProvider from '@refinedev/simple-rest';
|
|
64
|
+
import { Layout } from '@/components/refine-ui/layout/layout-01';
|
|
65
|
+
import { createAdminResources } from '@openenvx/admin';
|
|
66
|
+
import * as schema from '@/db/schema';
|
|
67
|
+
|
|
68
|
+
const resources = createAdminResources({
|
|
69
|
+
schema,
|
|
70
|
+
exclude: ['_drizzle_migrations'],
|
|
71
|
+
resources: {
|
|
72
|
+
users: {
|
|
73
|
+
label: 'Team Members',
|
|
74
|
+
meta: { icon: 'Users' }
|
|
75
|
+
},
|
|
76
|
+
posts: {
|
|
77
|
+
label: 'Blog Posts',
|
|
78
|
+
meta: { icon: 'FileText' }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export default function AdminLayout({ children }: { children: React.ReactNode }) {
|
|
84
|
+
return (
|
|
85
|
+
<Refine
|
|
86
|
+
routerProvider={routerProvider}
|
|
87
|
+
dataProvider={dataProvider('/api/admin')}
|
|
88
|
+
resources={resources}
|
|
89
|
+
options={{
|
|
90
|
+
syncWithLocation: true,
|
|
91
|
+
warnWhenUnsavedChanges: true,
|
|
92
|
+
useNewQueryKeys: true,
|
|
93
|
+
projectId: 'my-admin',
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
<Layout>{children}</Layout>
|
|
97
|
+
</Refine>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. Create List Page using Refine's shadcn/ui Components
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// app/admin/[resource]/page.tsx
|
|
106
|
+
'use client';
|
|
107
|
+
|
|
108
|
+
import { useMemo } from 'react';
|
|
109
|
+
import { useTable } from '@refinedev/react-table';
|
|
110
|
+
import type { ColumnDef } from '@tanstack/react-table';
|
|
111
|
+
import { useResourceParams } from '@refinedev/core';
|
|
112
|
+
import { ListView, ListViewHeader } from '@/components/refine-ui/views/list-view';
|
|
113
|
+
import { DataTable } from '@/components/refine-ui/data-table/data-table';
|
|
114
|
+
import { DataTableSorter } from '@/components/refine-ui/data-table/data-table-sorter';
|
|
115
|
+
import { DataTableFilterDropdownText } from '@/components/refine-ui/data-table/data-table-filter';
|
|
116
|
+
import { CreateButton } from '@/components/refine-ui/buttons/create-button';
|
|
117
|
+
|
|
118
|
+
interface Post {
|
|
119
|
+
id: number;
|
|
120
|
+
title: string;
|
|
121
|
+
status: string;
|
|
122
|
+
createdAt: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default function PostListPage() {
|
|
126
|
+
const { resource } = useResourceParams();
|
|
127
|
+
|
|
128
|
+
const columns = useMemo<ColumnDef<Post>[]>(
|
|
129
|
+
() => [
|
|
130
|
+
{
|
|
131
|
+
id: 'id',
|
|
132
|
+
accessorKey: 'id',
|
|
133
|
+
header: ({ column }) => (
|
|
134
|
+
<div className="flex items-center gap-1">
|
|
135
|
+
<span>ID</span>
|
|
136
|
+
<DataTableSorter column={column} />
|
|
137
|
+
</div>
|
|
138
|
+
),
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 'title',
|
|
142
|
+
accessorKey: 'title',
|
|
143
|
+
header: ({ column, table }) => (
|
|
144
|
+
<div className="flex items-center gap-1">
|
|
145
|
+
<span>Title</span>
|
|
146
|
+
<DataTableFilterDropdownText
|
|
147
|
+
defaultOperator="contains"
|
|
148
|
+
column={column}
|
|
149
|
+
table={table}
|
|
150
|
+
placeholder="Filter by title"
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
),
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 'status',
|
|
157
|
+
accessorKey: 'status',
|
|
158
|
+
header: 'Status',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 'createdAt',
|
|
162
|
+
accessorKey: 'createdAt',
|
|
163
|
+
header: ({ column }) => (
|
|
164
|
+
<div className="flex items-center gap-1">
|
|
165
|
+
<span>Created</span>
|
|
166
|
+
<DataTableSorter column={column} />
|
|
167
|
+
</div>
|
|
168
|
+
),
|
|
169
|
+
cell: ({ row }) => new Date(row.original.createdAt).toLocaleDateString(),
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
[]
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const table = useTable<Post>({
|
|
176
|
+
columns,
|
|
177
|
+
refineCoreProps: {
|
|
178
|
+
resource: resource?.name || 'posts',
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<ListView>
|
|
184
|
+
<ListViewHeader
|
|
185
|
+
title={resource?.meta?.label || resource?.name || 'Posts'}
|
|
186
|
+
headerButtons={<CreateButton resource={resource?.name} />}
|
|
187
|
+
/>
|
|
188
|
+
<DataTable table={table} />
|
|
189
|
+
</ListView>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 4. Create Edit Page
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// app/admin/[resource]/edit/[id]/page.tsx
|
|
198
|
+
'use client';
|
|
199
|
+
|
|
200
|
+
import { useForm } from '@refinedev/react-hook-form';
|
|
201
|
+
import { useResourceParams } from '@refinedev/core';
|
|
202
|
+
import { EditView, EditViewHeader } from '@/components/refine-ui/views/edit-view';
|
|
203
|
+
import { Input } from '@/components/ui/input';
|
|
204
|
+
import { Label } from '@/components/ui/label';
|
|
205
|
+
import {
|
|
206
|
+
Select,
|
|
207
|
+
SelectContent,
|
|
208
|
+
SelectItem,
|
|
209
|
+
SelectTrigger,
|
|
210
|
+
SelectValue,
|
|
211
|
+
} from '@/components/ui/select';
|
|
212
|
+
|
|
213
|
+
export default function PostEditPage() {
|
|
214
|
+
const { resource } = useResourceParams();
|
|
215
|
+
const {
|
|
216
|
+
refineCore: { onFinish, formLoading },
|
|
217
|
+
register,
|
|
218
|
+
handleSubmit,
|
|
219
|
+
formState: { errors },
|
|
220
|
+
} = useForm({
|
|
221
|
+
refineCoreProps: {
|
|
222
|
+
resource: resource?.name,
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<EditView>
|
|
228
|
+
<EditViewHeader title={`Edit ${resource?.meta?.label || resource?.name}`} />
|
|
229
|
+
<form onSubmit={handleSubmit(onFinish)}>
|
|
230
|
+
<div className="space-y-4">
|
|
231
|
+
<div>
|
|
232
|
+
<Label htmlFor="title">Title</Label>
|
|
233
|
+
<Input
|
|
234
|
+
id="title"
|
|
235
|
+
{...register('title', { required: 'Title is required' })}
|
|
236
|
+
/>
|
|
237
|
+
{errors.title && (
|
|
238
|
+
<span className="text-red-500 text-sm">{errors.title.message}</span>
|
|
239
|
+
)}
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<div>
|
|
243
|
+
<Label htmlFor="status">Status</Label>
|
|
244
|
+
<Select {...register('status')}>
|
|
245
|
+
<SelectTrigger>
|
|
246
|
+
<SelectValue placeholder="Select status" />
|
|
247
|
+
</SelectTrigger>
|
|
248
|
+
<SelectContent>
|
|
249
|
+
<SelectItem value="draft">Draft</SelectItem>
|
|
250
|
+
<SelectItem value="published">Published</SelectItem>
|
|
251
|
+
</SelectContent>
|
|
252
|
+
</Select>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<div>
|
|
256
|
+
<Label htmlFor="content">Content</Label>
|
|
257
|
+
<textarea
|
|
258
|
+
id="content"
|
|
259
|
+
{...register('content')}
|
|
260
|
+
className="w-full min-h-[200px] p-2 border rounded"
|
|
261
|
+
/>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
</form>
|
|
265
|
+
</EditView>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### 5. Create Show Page
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
// app/admin/[resource]/show/[id]/page.tsx
|
|
274
|
+
'use client';
|
|
275
|
+
|
|
276
|
+
import { useShow } from '@refinedev/core';
|
|
277
|
+
import { useResourceParams } from '@refinedev/core';
|
|
278
|
+
import { ShowView, ShowViewHeader } from '@/components/refine-ui/views/show-view';
|
|
279
|
+
import { TextField } from '@/components/refine-ui/fields/text-field';
|
|
280
|
+
import { DateField } from '@/components/refine-ui/fields/date-field';
|
|
281
|
+
|
|
282
|
+
interface Post {
|
|
283
|
+
id: number;
|
|
284
|
+
title: string;
|
|
285
|
+
content: string;
|
|
286
|
+
status: string;
|
|
287
|
+
createdAt: string;
|
|
288
|
+
updatedAt: string;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export default function PostShowPage() {
|
|
292
|
+
const { resource } = useResourceParams();
|
|
293
|
+
const { queryResult } = useShow<Post>({
|
|
294
|
+
resource: resource?.name,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const { data, isLoading } = queryResult;
|
|
298
|
+
const record = data?.data;
|
|
299
|
+
|
|
300
|
+
if (isLoading) {
|
|
301
|
+
return <div>Loading...</div>;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<ShowView>
|
|
306
|
+
<ShowViewHeader title={record?.title} />
|
|
307
|
+
<div className="space-y-4">
|
|
308
|
+
<div>
|
|
309
|
+
<label className="font-medium">Title</label>
|
|
310
|
+
<TextField value={record?.title} />
|
|
311
|
+
</div>
|
|
312
|
+
<div>
|
|
313
|
+
<label className="font-medium">Status</label>
|
|
314
|
+
<div className="capitalize">{record?.status}</div>
|
|
315
|
+
</div>
|
|
316
|
+
<div>
|
|
317
|
+
<label className="font-medium">Content</label>
|
|
318
|
+
<div className="whitespace-pre-wrap">{record?.content}</div>
|
|
319
|
+
</div>
|
|
320
|
+
<div className="grid grid-cols-2 gap-4">
|
|
321
|
+
<div>
|
|
322
|
+
<label className="font-medium">Created</label>
|
|
323
|
+
<DateField value={record?.createdAt} />
|
|
324
|
+
</div>
|
|
325
|
+
<div>
|
|
326
|
+
<label className="font-medium">Updated</label>
|
|
327
|
+
<DateField value={record?.updatedAt} />
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
</ShowView>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Configuration
|
|
337
|
+
|
|
338
|
+
### `createAdminResources(options)`
|
|
339
|
+
|
|
340
|
+
Generate Refine resources from your Drizzle schema.
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
const resources = createAdminResources({
|
|
344
|
+
// Your Drizzle schema exports
|
|
345
|
+
schema,
|
|
346
|
+
|
|
347
|
+
// Tables to exclude
|
|
348
|
+
exclude: ['migrations', 'sessions'],
|
|
349
|
+
|
|
350
|
+
// Custom configurations per resource
|
|
351
|
+
resources: {
|
|
352
|
+
users: {
|
|
353
|
+
label: 'Team Members', // Display name
|
|
354
|
+
icon: 'Users', // Lucide icon name
|
|
355
|
+
hidden: false, // Hide from navigation
|
|
356
|
+
meta: { // Custom metadata
|
|
357
|
+
icon: 'Users',
|
|
358
|
+
label: 'Team Members',
|
|
359
|
+
canDelete: false, // Disable delete button
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
// Default icon for resources
|
|
365
|
+
defaultIcon: 'FileText'
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### `createDrizzleDataProvider(options)`
|
|
370
|
+
|
|
371
|
+
Create a Refine data provider for Drizzle ORM.
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
const dataProvider = createDrizzleDataProvider({
|
|
375
|
+
// Drizzle database instance with query support
|
|
376
|
+
db,
|
|
377
|
+
|
|
378
|
+
// Resource name to table name mapping
|
|
379
|
+
resources: {
|
|
380
|
+
users: 'users',
|
|
381
|
+
posts: 'posts',
|
|
382
|
+
// 'resource-name': 'table_name'
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Available Refine shadcn/ui Components
|
|
388
|
+
|
|
389
|
+
### Views
|
|
390
|
+
- `ListView` / `ListViewHeader` - List page layout
|
|
391
|
+
- `CreateView` / `CreateViewHeader` - Create page layout
|
|
392
|
+
- `EditView` / `EditViewHeader` - Edit page layout
|
|
393
|
+
- `ShowView` / `ShowViewHeader` - Show page layout
|
|
394
|
+
|
|
395
|
+
### Data Table
|
|
396
|
+
- `DataTable` - Advanced table with sorting, filtering, pagination
|
|
397
|
+
- `DataTableSorter` - Column sort buttons
|
|
398
|
+
- `DataTableFilterDropdownText` - Text filter dropdown
|
|
399
|
+
- `DataTableColumnHeader` - Column header with sort/filter
|
|
400
|
+
|
|
401
|
+
### Buttons
|
|
402
|
+
- `CreateButton` - Navigate to create page
|
|
403
|
+
- `EditButton` - Navigate to edit page
|
|
404
|
+
- `DeleteButton` - Delete with confirmation
|
|
405
|
+
- `ShowButton` - Navigate to show page
|
|
406
|
+
- `ListButton` - Navigate to list page
|
|
407
|
+
- `SaveButton` - Save form
|
|
408
|
+
- `RefreshButton` - Refresh data
|
|
409
|
+
|
|
410
|
+
### Fields
|
|
411
|
+
- `TextField` - Display text
|
|
412
|
+
- `DateField` - Display dates
|
|
413
|
+
- `BooleanField` - Display booleans
|
|
414
|
+
- `NumberField` - Display numbers
|
|
415
|
+
- `EmailField` - Display emails
|
|
416
|
+
- `UrlField` - Display URLs
|
|
417
|
+
- `TagField` - Display tags
|
|
418
|
+
- `MarkdownField` - Display markdown
|
|
419
|
+
|
|
420
|
+
### Layout
|
|
421
|
+
- `Layout` - Complete app layout with sidebar, header, theme support
|
|
422
|
+
- `ThemedLayout` - Layout with theme provider
|
|
423
|
+
- `Breadcrumb` - Navigation breadcrumb
|
|
424
|
+
|
|
425
|
+
### Forms
|
|
426
|
+
- `Form` - Form wrapper with validation
|
|
427
|
+
- `AutoSaveIndicator` - Shows auto-save status
|
|
428
|
+
|
|
429
|
+
### Auth
|
|
430
|
+
- `SignInForm` - Login form
|
|
431
|
+
- `SignUpForm` - Registration form
|
|
432
|
+
- `ForgotPasswordForm` - Password reset
|
|
433
|
+
|
|
434
|
+
### Utilities
|
|
435
|
+
- `ErrorComponent` - Error boundary
|
|
436
|
+
- `NotificationProvider` - Toast notifications
|
|
437
|
+
- `CanAccess` - Access control wrapper
|
|
438
|
+
- `Authenticated` - Auth guard wrapper
|
|
439
|
+
|
|
440
|
+
## Resources
|
|
441
|
+
|
|
442
|
+
- [Refine Documentation](https://refine.dev/)
|
|
443
|
+
- [Refine shadcn/ui Integration](https://refine.dev/core/docs/ui-integrations/shadcn/introduction/)
|
|
444
|
+
- [shadcn/ui Documentation](https://ui.shadcn.com/)
|
|
445
|
+
- [Drizzle ORM Documentation](https://orm.drizzle.team/)
|
|
446
|
+
|
|
447
|
+
## License
|
|
448
|
+
|
|
449
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side exports for @openenvx/admin
|
|
3
|
+
*
|
|
4
|
+
* This package provides utilities to set up Refine with Drizzle ORM.
|
|
5
|
+
*
|
|
6
|
+
* For UI components, use Refine's shadcn/ui registry:
|
|
7
|
+
* npx shadcn@latest add https://ui.refine.dev/r/views.json
|
|
8
|
+
* npx shadcn@latest add https://ui.refine.dev/r/data-table.json
|
|
9
|
+
* npx shadcn@latest add https://ui.refine.dev/r/layout/layout-01.json
|
|
10
|
+
*
|
|
11
|
+
* @see https://refine.dev/core/docs/ui-integrations/shadcn/introduction/
|
|
12
|
+
*/
|
|
13
|
+
export type { AdminResource, AdminResourcesConfig } from './resources';
|
|
14
|
+
export { createAdminResources } from './resources';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
//#region src/client/resources.ts
|
|
2
|
+
/**
|
|
3
|
+
* Generate Refine resources from Drizzle schema
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import * as schema from './db/schema';
|
|
8
|
+
*
|
|
9
|
+
* const resources = createAdminResources({
|
|
10
|
+
* schema,
|
|
11
|
+
* exclude: ['migrations'],
|
|
12
|
+
* resources: {
|
|
13
|
+
* users: {
|
|
14
|
+
* label: 'Team Members',
|
|
15
|
+
* meta: { icon: 'Users' }
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function createAdminResources(config) {
|
|
22
|
+
const { schema, exclude = [], resources = {}, defaultIcon = "FileText" } = config;
|
|
23
|
+
const adminResources = [];
|
|
24
|
+
for (const [name, table] of Object.entries(schema)) {
|
|
25
|
+
if (exclude.includes(name) || isRelation(table)) continue;
|
|
26
|
+
if (!isTable(table)) continue;
|
|
27
|
+
const tableName = getTableName(table) || name;
|
|
28
|
+
const customConfig = resources[name] || {};
|
|
29
|
+
if (customConfig.hidden) continue;
|
|
30
|
+
const resource = {
|
|
31
|
+
name: tableName,
|
|
32
|
+
list: `/${tableName}`,
|
|
33
|
+
create: `/${tableName}/create`,
|
|
34
|
+
edit: `/${tableName}/edit/:id`,
|
|
35
|
+
show: `/${tableName}/show/:id`,
|
|
36
|
+
label: customConfig.label || capitalize(tableName),
|
|
37
|
+
icon: customConfig.icon || defaultIcon,
|
|
38
|
+
meta: {
|
|
39
|
+
label: customConfig.label || capitalize(tableName),
|
|
40
|
+
icon: customConfig.icon || defaultIcon,
|
|
41
|
+
...customConfig.meta
|
|
42
|
+
},
|
|
43
|
+
...customConfig
|
|
44
|
+
};
|
|
45
|
+
adminResources.push(resource);
|
|
46
|
+
}
|
|
47
|
+
return adminResources;
|
|
48
|
+
}
|
|
49
|
+
function isTable(value) {
|
|
50
|
+
if (typeof value !== "object" || value === null) return false;
|
|
51
|
+
const table = value;
|
|
52
|
+
return Symbol.for("drizzle:PgTable") in table || Symbol.for("drizzle:MySqlTable") in table || Symbol.for("drizzle:SQLiteTable") in table || typeof table[""] === "object" && table[""] !== null;
|
|
53
|
+
}
|
|
54
|
+
function isRelation(value) {
|
|
55
|
+
if (typeof value !== "object" || value === null) return false;
|
|
56
|
+
const rel = value;
|
|
57
|
+
return Symbol.for("drizzle:Relations") in rel || rel.config !== void 0 && typeof rel.config === "object";
|
|
58
|
+
}
|
|
59
|
+
function getTableName(table) {
|
|
60
|
+
if (typeof table !== "object" || table === null) return null;
|
|
61
|
+
const t = table;
|
|
62
|
+
const nameSymbol = Symbol.for("drizzle:Name");
|
|
63
|
+
if (nameSymbol in t) return t[nameSymbol];
|
|
64
|
+
if (typeof t[""] === "object" && t[""] !== null) {
|
|
65
|
+
const config = t[""];
|
|
66
|
+
const configNameSymbol = Symbol.for("drizzle:Name");
|
|
67
|
+
if (configNameSymbol in config) return config[configNameSymbol];
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
function capitalize(str) {
|
|
72
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { createAdminResources };
|
|
76
|
+
|
|
77
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/client/resources.ts"],"sourcesContent":["/**\n * Generate Refine resources from Drizzle schema\n */\n\nimport type { ResourceProps } from '@refinedev/core';\n\nexport interface AdminResource extends ResourceProps {\n hidden?: boolean;\n icon?: string;\n label?: string;\n meta?: {\n label?: string;\n icon?: string;\n [key: string]: unknown;\n };\n}\n\nexport interface AdminResourcesConfig {\n /** Default icon for resources */\n defaultIcon?: string;\n /** Tables to exclude from the admin panel */\n exclude?: string[];\n /** Custom resource configurations */\n resources?: Record<string, Partial<AdminResource>>;\n /** Schema module exported from your Drizzle schema file */\n schema: Record<string, unknown>;\n}\n\n/**\n * Generate Refine resources from Drizzle schema\n *\n * @example\n * ```typescript\n * import * as schema from './db/schema';\n *\n * const resources = createAdminResources({\n * schema,\n * exclude: ['migrations'],\n * resources: {\n * users: {\n * label: 'Team Members',\n * meta: { icon: 'Users' }\n * }\n * }\n * });\n * ```\n */\nexport function createAdminResources(\n config: AdminResourcesConfig\n): AdminResource[] {\n const {\n schema,\n exclude = [],\n resources = {},\n defaultIcon = 'FileText',\n } = config;\n\n const adminResources: AdminResource[] = [];\n\n for (const [name, table] of Object.entries(schema)) {\n // Skip if it's a relation definition or excluded\n if (exclude.includes(name) || isRelation(table)) {\n continue;\n }\n\n // Check if it's a table (has the Drizzle table symbol)\n if (!isTable(table)) {\n continue;\n }\n\n // Get table name from the table object\n const tableName = getTableName(table) || name;\n\n // Get custom config for this resource\n const customConfig = resources[name] || {};\n\n // Skip if explicitly hidden\n if (customConfig.hidden) {\n continue;\n }\n\n const resource: AdminResource = {\n name: tableName,\n list: `/${tableName}`,\n create: `/${tableName}/create`,\n edit: `/${tableName}/edit/:id`,\n show: `/${tableName}/show/:id`,\n label: customConfig.label || capitalize(tableName),\n icon: customConfig.icon || defaultIcon,\n meta: {\n label: customConfig.label || capitalize(tableName),\n icon: customConfig.icon || defaultIcon,\n ...customConfig.meta,\n },\n ...customConfig,\n };\n\n adminResources.push(resource);\n }\n\n return adminResources;\n}\n\nfunction isTable(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n\n const table = value as Record<string | symbol, unknown>;\n\n // Check for Drizzle table symbols\n return (\n Symbol.for('drizzle:PgTable') in table ||\n Symbol.for('drizzle:MySqlTable') in table ||\n Symbol.for('drizzle:SQLiteTable') in table ||\n // Fallback: check if it has table-specific properties\n (typeof table[''] === 'object' && table[''] !== null)\n );\n}\n\nfunction isRelation(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n\n const rel = value as Record<string | symbol, unknown>;\n\n return (\n Symbol.for('drizzle:Relations') in rel ||\n (rel.config !== undefined && typeof rel.config === 'object')\n );\n}\n\nfunction getTableName(table: unknown): string | null {\n if (typeof table !== 'object' || table === null) {\n return null;\n }\n\n const t = table as Record<string | symbol, unknown>;\n\n // Try to get name from symbol\n const nameSymbol = Symbol.for('drizzle:Name');\n if (nameSymbol in t) {\n return t[nameSymbol] as string;\n }\n\n // Try to get from internal config\n if (typeof t[''] === 'object' && t[''] !== null) {\n const config = t[''] as Record<symbol, unknown>;\n const configNameSymbol = Symbol.for('drizzle:Name');\n if (configNameSymbol in config) {\n return config[configNameSymbol] as string;\n }\n }\n\n return null;\n}\n\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA+CA,SAAgB,qBACd,QACiB;CACjB,MAAM,EACJ,QACA,UAAU,EAAE,EACZ,YAAY,EAAE,EACd,cAAc,eACZ;CAEJ,MAAM,iBAAkC,EAAE;AAE1C,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,EAAE;AAElD,MAAI,QAAQ,SAAS,KAAK,IAAI,WAAW,MAAM,CAC7C;AAIF,MAAI,CAAC,QAAQ,MAAM,CACjB;EAIF,MAAM,YAAY,aAAa,MAAM,IAAI;EAGzC,MAAM,eAAe,UAAU,SAAS,EAAE;AAG1C,MAAI,aAAa,OACf;EAGF,MAAM,WAA0B;GAC9B,MAAM;GACN,MAAM,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,UAAU;GACpB,MAAM,IAAI,UAAU;GACpB,OAAO,aAAa,SAAS,WAAW,UAAU;GAClD,MAAM,aAAa,QAAQ;GAC3B,MAAM;IACJ,OAAO,aAAa,SAAS,WAAW,UAAU;IAClD,MAAM,aAAa,QAAQ;IAC3B,GAAG,aAAa;IACjB;GACD,GAAG;GACJ;AAED,iBAAe,KAAK,SAAS;;AAG/B,QAAO;;AAGT,SAAS,QAAQ,OAAyB;AACxC,KAAI,OAAO,UAAU,YAAY,UAAU,KACzC,QAAO;CAGT,MAAM,QAAQ;AAGd,QACE,OAAO,IAAI,kBAAkB,IAAI,SACjC,OAAO,IAAI,qBAAqB,IAAI,SACpC,OAAO,IAAI,sBAAsB,IAAI,SAEpC,OAAO,MAAM,QAAQ,YAAY,MAAM,QAAQ;;AAIpD,SAAS,WAAW,OAAyB;AAC3C,KAAI,OAAO,UAAU,YAAY,UAAU,KACzC,QAAO;CAGT,MAAM,MAAM;AAEZ,QACE,OAAO,IAAI,oBAAoB,IAAI,OAClC,IAAI,WAAW,KAAA,KAAa,OAAO,IAAI,WAAW;;AAIvD,SAAS,aAAa,OAA+B;AACnD,KAAI,OAAO,UAAU,YAAY,UAAU,KACzC,QAAO;CAGT,MAAM,IAAI;CAGV,MAAM,aAAa,OAAO,IAAI,eAAe;AAC7C,KAAI,cAAc,EAChB,QAAO,EAAE;AAIX,KAAI,OAAO,EAAE,QAAQ,YAAY,EAAE,QAAQ,MAAM;EAC/C,MAAM,SAAS,EAAE;EACjB,MAAM,mBAAmB,OAAO,IAAI,eAAe;AACnD,MAAI,oBAAoB,OACtB,QAAO,OAAO;;AAIlB,QAAO;;AAGT,SAAS,WAAW,KAAqB;AACvC,QAAO,IAAI,OAAO,EAAE,CAAC,aAAa,GAAG,IAAI,MAAM,EAAE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate Refine resources from Drizzle schema
|
|
3
|
+
*/
|
|
4
|
+
import type { ResourceProps } from '@refinedev/core';
|
|
5
|
+
export interface AdminResource extends ResourceProps {
|
|
6
|
+
hidden?: boolean;
|
|
7
|
+
icon?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
meta?: {
|
|
10
|
+
label?: string;
|
|
11
|
+
icon?: string;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface AdminResourcesConfig {
|
|
16
|
+
/** Default icon for resources */
|
|
17
|
+
defaultIcon?: string;
|
|
18
|
+
/** Tables to exclude from the admin panel */
|
|
19
|
+
exclude?: string[];
|
|
20
|
+
/** Custom resource configurations */
|
|
21
|
+
resources?: Record<string, Partial<AdminResource>>;
|
|
22
|
+
/** Schema module exported from your Drizzle schema file */
|
|
23
|
+
schema: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generate Refine resources from Drizzle schema
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import * as schema from './db/schema';
|
|
31
|
+
*
|
|
32
|
+
* const resources = createAdminResources({
|
|
33
|
+
* schema,
|
|
34
|
+
* exclude: ['migrations'],
|
|
35
|
+
* resources: {
|
|
36
|
+
* users: {
|
|
37
|
+
* label: 'Team Members',
|
|
38
|
+
* meta: { icon: 'Users' }
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function createAdminResources(config: AdminResourcesConfig): AdminResource[];
|
|
45
|
+
//# sourceMappingURL=resources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/client/resources.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,WAAW,aAAc,SAAQ,aAAa;IAClD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,iCAAiC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;IACnD,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,oBAAoB,GAC3B,aAAa,EAAE,CAoDjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAElE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema introspector using Drizzle's native APIs
|
|
3
|
+
* Imports the schema module and extracts metadata using getTableConfig
|
|
4
|
+
*/
|
|
5
|
+
import type { AdminSchema, TableSchema } from '../core/types';
|
|
6
|
+
/**
|
|
7
|
+
* Introspect a Drizzle schema module and extract all table metadata
|
|
8
|
+
*/
|
|
9
|
+
export declare function introspectSchema(schemaPath: string): Promise<AdminSchema>;
|
|
10
|
+
/**
|
|
11
|
+
* Extract relations from the schema module
|
|
12
|
+
* Relations are defined using the `relations()` function from drizzle-orm
|
|
13
|
+
*/
|
|
14
|
+
export declare function introspectRelations(schemaPath: string): Promise<Record<string, unknown>>;
|
|
15
|
+
/**
|
|
16
|
+
* Get list of all tables in the schema
|
|
17
|
+
*/
|
|
18
|
+
export declare function getTableNames(schemaPath: string): Promise<string[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Get a specific table's schema
|
|
21
|
+
*/
|
|
22
|
+
export declare function getTableSchema(schemaPath: string, tableName: string): Promise<TableSchema | undefined>;
|
|
23
|
+
//# sourceMappingURL=introspector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"introspector.d.ts","sourceRoot":"","sources":["../../src/core/introspector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAgB,WAAW,EAAE,MAAM,eAAe,CAAC;AAM5E;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,WAAW,CAAC,CA4BtB;AAyHD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAYlC;AAeD;;GAEG;AACH,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGzE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAGlC"}
|