next-data-kit 2.0.0 → 3.0.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 +200 -170
- package/dist/client/hooks/useDataKit.d.ts.map +1 -1
- package/dist/client/hooks/useDataKit.js +20 -18
- package/dist/client/hooks/useDataKit.js.map +1 -1
- package/dist/index.cjs +4 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +4 -18
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +2 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +0 -1
- package/dist/server.d.ts +0 -1
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,14 +4,14 @@ A powerful table utility for server-side pagination, filtering, and sorting with
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
7
|
+
- 🚀 **Server-side pagination** - Efficient data fetching with page-based navigation
|
|
8
|
+
- 🔍 **Flexible filtering** - Support for regex, exact match, and custom filters
|
|
9
|
+
- 📊 **Multi-column sorting** - Sort by multiple columns with customizable order
|
|
10
|
+
- ⚛️ **React hooks** - `useDataKit`, `useSelection`, `usePagination` for state management
|
|
11
|
+
- 🎨 **Components** - `DataKitTable` for tables, `DataKit` for custom layouts
|
|
12
|
+
- 📝 **TypeScript** - Fully typed with generics support
|
|
13
|
+
- 🔌 **Framework agnostic** - Works with any database ORM/ODM (Mongoose, Prisma, etc.)
|
|
14
|
+
- 📦 **Tree-shakeable** - Import only what you need
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
@@ -28,17 +28,17 @@ pnpm add next-data-kit
|
|
|
28
28
|
### Server-side (Next.js Server Action)
|
|
29
29
|
|
|
30
30
|
```typescript
|
|
31
|
-
|
|
31
|
+
'use server';
|
|
32
32
|
|
|
33
|
-
import { dataKitServerAction, createSearchFilter } from
|
|
34
|
-
import type { TDataKitInput } from
|
|
35
|
-
import UserModel from
|
|
33
|
+
import { dataKitServerAction, createSearchFilter } from 'next-data-kit/server';
|
|
34
|
+
import type { TDataKitInput } from 'next-data-kit/types';
|
|
35
|
+
import UserModel from '@/models/User';
|
|
36
36
|
|
|
37
37
|
export async function fetchUsers(input: TDataKitInput) {
|
|
38
38
|
return dataKitServerAction({
|
|
39
39
|
model: UserModel,
|
|
40
40
|
input,
|
|
41
|
-
item: async
|
|
41
|
+
item: async user => ({
|
|
42
42
|
id: user._id.toString(),
|
|
43
43
|
name: user.name,
|
|
44
44
|
email: user.email,
|
|
@@ -47,38 +47,41 @@ export async function fetchUsers(input: TDataKitInput) {
|
|
|
47
47
|
active: true,
|
|
48
48
|
}),
|
|
49
49
|
filterCustom: {
|
|
50
|
-
search: createSearchFilter([
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
search: createSearchFilter(['name', 'email']),
|
|
51
|
+
age: value => ({
|
|
52
|
+
age: {
|
|
53
|
+
$gte: value,
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
56
|
},
|
|
57
|
-
|
|
57
|
+
// Only 'search' and 'age' filters are allowed (auto-extracted from filterCustom keys)
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
63
62
|
### Input Validation (Optional)
|
|
64
63
|
|
|
65
64
|
You can use the built-in Zod schema to validate inputs before processing:
|
|
66
65
|
|
|
67
66
|
```typescript
|
|
68
|
-
|
|
67
|
+
'use server';
|
|
69
68
|
|
|
70
|
-
import { dataKitServerAction, dataKitSchemaZod } from
|
|
69
|
+
import { dataKitServerAction, dataKitSchemaZod } from 'next-data-kit/server';
|
|
71
70
|
|
|
72
71
|
export async function fetchUsers(input: unknown) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
// Validate input
|
|
73
|
+
const parsedInput = dataKitSchemaZod.parse(input);
|
|
74
|
+
|
|
75
|
+
return dataKitServerAction({
|
|
76
|
+
model: UserModel,
|
|
77
|
+
input: parsedInput,
|
|
78
|
+
item: user => ({ id: user._id.toString(), name: user.name }),
|
|
79
|
+
filterCustom: {
|
|
80
|
+
search: value => ({ name: { $regex: value, $options: 'i' } }),
|
|
81
|
+
role: value => ({ role: value }),
|
|
82
|
+
},
|
|
83
|
+
// Only 'search' and 'role' filters are allowed
|
|
84
|
+
});
|
|
82
85
|
}
|
|
83
86
|
```
|
|
84
87
|
|
|
@@ -87,10 +90,10 @@ export async function fetchUsers(input: unknown) {
|
|
|
87
90
|
Ready-to-use table with built-in filtering, sorting, and selection:
|
|
88
91
|
|
|
89
92
|
```tsx
|
|
90
|
-
|
|
93
|
+
'use client';
|
|
91
94
|
|
|
92
|
-
import { DataKitTable } from
|
|
93
|
-
import { fetchUsers } from
|
|
95
|
+
import { DataKitTable } from 'next-data-kit/client';
|
|
96
|
+
import { fetchUsers } from '@/actions/users';
|
|
94
97
|
|
|
95
98
|
export function UsersTable() {
|
|
96
99
|
return (
|
|
@@ -98,14 +101,14 @@ export function UsersTable() {
|
|
|
98
101
|
action={fetchUsers}
|
|
99
102
|
limit={{ default: 10 }}
|
|
100
103
|
filters={[
|
|
101
|
-
{ id:
|
|
104
|
+
{ id: 'search', label: 'Search', type: 'TEXT', placeholder: 'Search...' },
|
|
102
105
|
{
|
|
103
|
-
id:
|
|
104
|
-
label:
|
|
105
|
-
type:
|
|
106
|
+
id: 'role',
|
|
107
|
+
label: 'Role',
|
|
108
|
+
type: 'SELECT',
|
|
106
109
|
dataset: [
|
|
107
|
-
{ id:
|
|
108
|
-
{ id:
|
|
110
|
+
{ id: 'admin', name: 'admin', label: 'Admin' },
|
|
111
|
+
{ id: 'user', name: 'user', label: 'User' },
|
|
109
112
|
],
|
|
110
113
|
},
|
|
111
114
|
]}
|
|
@@ -113,9 +116,9 @@ export function UsersTable() {
|
|
|
113
116
|
enabled: true,
|
|
114
117
|
actions: {
|
|
115
118
|
delete: {
|
|
116
|
-
name:
|
|
117
|
-
function: async
|
|
118
|
-
await deleteUsers(items.map(
|
|
119
|
+
name: 'Delete Selected',
|
|
120
|
+
function: async items => {
|
|
121
|
+
await deleteUsers(items.map(i => i.id));
|
|
119
122
|
return [true, { deselectAll: true }];
|
|
120
123
|
},
|
|
121
124
|
},
|
|
@@ -125,7 +128,7 @@ export function UsersTable() {
|
|
|
125
128
|
{
|
|
126
129
|
head: <DataKitTable.Head>Name</DataKitTable.Head>,
|
|
127
130
|
body: ({ item }) => <DataKitTable.Cell>{item.name}</DataKitTable.Cell>,
|
|
128
|
-
sortable: { path:
|
|
131
|
+
sortable: { path: 'name', default: 0 },
|
|
129
132
|
},
|
|
130
133
|
{
|
|
131
134
|
head: <DataKitTable.Head>Email</DataKitTable.Head>,
|
|
@@ -142,22 +145,18 @@ export function UsersTable() {
|
|
|
142
145
|
Use `DataKit` for grids, cards, or any custom layout. It provides toolbar/pagination but lets you render content:
|
|
143
146
|
|
|
144
147
|
```tsx
|
|
145
|
-
|
|
148
|
+
'use client';
|
|
146
149
|
|
|
147
|
-
import { DataKit } from
|
|
148
|
-
import { fetchUsers } from
|
|
150
|
+
import { DataKit } from 'next-data-kit/client';
|
|
151
|
+
import { fetchUsers } from '@/actions/users';
|
|
149
152
|
|
|
150
153
|
export function UsersGrid() {
|
|
151
154
|
return (
|
|
152
|
-
<DataKit
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
{(dataKit) => (
|
|
158
|
-
<div className="grid grid-cols-4 gap-4">
|
|
159
|
-
{dataKit.items.map((user) => (
|
|
160
|
-
<div key={user.id} className="rounded-lg border p-4">
|
|
155
|
+
<DataKit action={fetchUsers} limit={{ default: 12 }} filters={[{ id: 'search', label: 'Search', type: 'TEXT' }]}>
|
|
156
|
+
{dataKit => (
|
|
157
|
+
<div className='grid grid-cols-4 gap-4'>
|
|
158
|
+
{dataKit.items.map(user => (
|
|
159
|
+
<div key={user.id} className='rounded-lg border p-4'>
|
|
161
160
|
<h3>{user.name}</h3>
|
|
162
161
|
<p>{user.email}</p>
|
|
163
162
|
</div>
|
|
@@ -173,10 +172,12 @@ export function UsersGrid() {
|
|
|
173
172
|
|
|
174
173
|
```tsx
|
|
175
174
|
<DataKit action={fetchUsers} manual>
|
|
176
|
-
{
|
|
175
|
+
{dataKit => (
|
|
177
176
|
<>
|
|
178
177
|
{dataKit.state.isLoading && <Spinner />}
|
|
179
|
-
{dataKit.items.map(
|
|
178
|
+
{dataKit.items.map(user => (
|
|
179
|
+
<Card key={user.id} user={user} />
|
|
180
|
+
))}
|
|
180
181
|
</>
|
|
181
182
|
)}
|
|
182
183
|
</DataKit>
|
|
@@ -187,10 +188,10 @@ export function UsersGrid() {
|
|
|
187
188
|
For fully custom implementations:
|
|
188
189
|
|
|
189
190
|
```tsx
|
|
190
|
-
|
|
191
|
+
'use client';
|
|
191
192
|
|
|
192
|
-
import { useDataKit } from
|
|
193
|
-
import { fetchUsers } from
|
|
193
|
+
import { useDataKit } from 'next-data-kit/client';
|
|
194
|
+
import { fetchUsers } from '@/actions/users';
|
|
194
195
|
|
|
195
196
|
export function UsersTable() {
|
|
196
197
|
const {
|
|
@@ -208,7 +209,7 @@ export function UsersTable() {
|
|
|
208
209
|
|
|
209
210
|
return (
|
|
210
211
|
<div>
|
|
211
|
-
<input placeholder=
|
|
212
|
+
<input placeholder='Search...' onChange={e => setFilter('search', e.target.value)} />
|
|
212
213
|
|
|
213
214
|
{isLoading ? (
|
|
214
215
|
<p>Loading...</p>
|
|
@@ -216,12 +217,12 @@ export function UsersTable() {
|
|
|
216
217
|
<table>
|
|
217
218
|
<thead>
|
|
218
219
|
<tr>
|
|
219
|
-
<th onClick={() => setSort(
|
|
220
|
-
<th onClick={() => setSort(
|
|
220
|
+
<th onClick={() => setSort('name', 1)}>Name</th>
|
|
221
|
+
<th onClick={() => setSort('email', 1)}>Email</th>
|
|
221
222
|
</tr>
|
|
222
223
|
</thead>
|
|
223
224
|
<tbody>
|
|
224
|
-
{items.map(
|
|
225
|
+
{items.map(user => (
|
|
225
226
|
<tr key={user.id}>
|
|
226
227
|
<td>{user.name}</td>
|
|
227
228
|
<td>{user.email}</td>
|
|
@@ -255,12 +256,16 @@ type TDataKitServerActionOptions<T, R> = {
|
|
|
255
256
|
model: TMongoModel<T>;
|
|
256
257
|
item: (item: T) => Promise<R> | R;
|
|
257
258
|
filter?: (filterInput?: Record<string, unknown>) => TMongoFilterQuery<T>;
|
|
259
|
+
// ** Custom filter configuration (defines allowed filter keys)
|
|
258
260
|
filterCustom?: TFilterCustomConfigWithFilter<T, TMongoFilterQuery<T>>;
|
|
259
261
|
defaultSort?: TSortOptions<T>;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
+
// ** Maximum limit per page (default: 100)
|
|
263
|
+
maxLimit?: number;
|
|
264
|
+
// ** Whitelist of allowed query fields
|
|
265
|
+
queryAllowed?: string[];
|
|
262
266
|
};
|
|
263
267
|
```
|
|
268
|
+
|
|
264
269
|
```
|
|
265
270
|
|
|
266
271
|
// ... inside dataKitServerAction options
|
|
@@ -268,59 +273,84 @@ type TDataKitServerActionOptions<T, R> = {
|
|
|
268
273
|
}
|
|
269
274
|
```
|
|
270
275
|
|
|
271
|
-
###
|
|
276
|
+
### Understanding `filter` vs `query`
|
|
272
277
|
|
|
273
|
-
|
|
278
|
+
There are two ways data reaches your database:
|
|
279
|
+
|
|
280
|
+
1. **`filter` (via `filterCustom`)** - For user-facing filters with transformations
|
|
281
|
+
- Client-side `filters` prop → sends values to `filter` parameter
|
|
282
|
+
- Server validates against `filterCustom` keys
|
|
283
|
+
- You define how values transform into database queries
|
|
284
|
+
- **Use for**: search boxes, dropdowns, date ranges, etc.
|
|
285
|
+
|
|
286
|
+
2. **`query` (via `queryAllowed`)** - For direct field matching
|
|
287
|
+
- Direct database field equality checks
|
|
288
|
+
- Must explicitly whitelist with `queryAllowed`
|
|
289
|
+
- **Use for**: fixed filters like `{ active: true }`, user-specific queries
|
|
274
290
|
|
|
275
291
|
```typescript
|
|
292
|
+
dataKitServerAction({
|
|
293
|
+
model: UserModel,
|
|
294
|
+
input,
|
|
295
|
+
item: u => u,
|
|
296
|
+
// Client filters go through filterCustom
|
|
297
|
+
filterCustom: {
|
|
298
|
+
search: createSearchFilter(['name', 'email']),
|
|
299
|
+
role: value => ({ role: value }),
|
|
300
|
+
},
|
|
301
|
+
// Direct queries need explicit whitelist
|
|
302
|
+
queryAllowed: ['organizationId', 'active'],
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Security Note: Strict Mode by Default
|
|
307
|
+
|
|
308
|
+
**Filter Security**: When you define `filterCustom`, ONLY those keys are allowed. Any other filter key from the client will **THROW AN ERROR**.
|
|
309
|
+
|
|
310
|
+
**Query Security**: When you provide `queryAllowed`, only those query fields are accepted. Any other query field will throw an error.
|
|
311
|
+
|
|
312
|
+
````typescript
|
|
276
313
|
// Strict Security Example
|
|
277
314
|
dataKitServerAction({
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
315
|
+
model: UserModel,
|
|
316
|
+
input,
|
|
317
|
+
item: u => ({ id: u._id.toString(), name: u.name }),
|
|
318
|
+
filterCustom: {
|
|
319
|
+
name: value => ({ name: { $regex: value, $options: 'i' } }),
|
|
320
|
+
email: value => ({ email: { $regex: value, $options: 'i' } }),
|
|
321
|
+
role: value => ({ role: value }),
|
|
322
|
+
},
|
|
323
|
+
// ONLY 'name', 'email', and 'role' filters are allowed
|
|
324
|
+
// If client sends { filter: { secret: "true" } }, this WILL THROW an Error!
|
|
325
|
+
|
|
326
|
+
// Query params need explicit whitelist
|
|
327
|
+
queryAllowed: ['status'],
|
|
328
|
+
});
|
|
286
329
|
```
|
|
287
330
|
|
|
288
331
|
### Error Handling on Client
|
|
289
332
|
|
|
290
|
-
When the server action throws an
|
|
333
|
+
When the server action throws an error (e.g., security violation), the client automatically handles it:
|
|
334
|
+
|
|
335
|
+
**`DataKitTable`**: Displays error in red within the table body
|
|
291
336
|
|
|
292
|
-
|
|
293
|
-
- **`useDataKit`**: The error is available in `state.error` for custom handling.
|
|
337
|
+
**`useDataKit`**: Error available in `state.error`
|
|
294
338
|
|
|
295
339
|
```tsx
|
|
296
|
-
|
|
297
|
-
const { state: { error } } = useDataKit({ ... });
|
|
340
|
+
const { state: { error } } = useDataKit({ action: fetchUsers });
|
|
298
341
|
|
|
299
342
|
if (error) {
|
|
300
|
-
|
|
343
|
+
return <div className="text-red-500">Error: {error.message}</div>;
|
|
301
344
|
}
|
|
302
345
|
```
|
|
303
346
|
|
|
304
|
-
### Input Validation (Optional)
|
|
305
|
-
type TDataKitServerActionOptions<T, R> = {
|
|
306
|
-
input: TDataKitInput<T>;
|
|
307
|
-
model: TMongoModel<T>;
|
|
308
|
-
item: (item: T) => Promise<R> | R;
|
|
309
|
-
filter?: (filterInput?: Record<string, unknown>) => TMongoFilterQuery<T>;
|
|
310
|
-
filterCustom?: TFilterCustomConfigWithFilter<T, TMongoFilterQuery<T>>;
|
|
311
|
-
defaultSort?: TSortOptions<T>;
|
|
312
|
-
// ** Whitelist of allowed filter fields (crucial for security when using query prop)
|
|
313
|
-
filterAllowed?: string[];
|
|
314
|
-
};
|
|
315
|
-
```
|
|
316
|
-
|
|
317
347
|
#### `createSearchFilter(fields)`
|
|
318
348
|
|
|
319
|
-
Create a search filter for multiple fields.
|
|
349
|
+
Create a search filter for multiple fields. Use this in `filterCustom`.
|
|
320
350
|
|
|
321
351
|
```typescript
|
|
322
352
|
filterCustom: {
|
|
323
|
-
search: createSearchFilter([
|
|
353
|
+
search: createSearchFilter(['name', 'email', 'phone']),
|
|
324
354
|
}
|
|
325
355
|
```
|
|
326
356
|
|
|
@@ -353,38 +383,38 @@ filterCustom: {
|
|
|
353
383
|
price: { $gte: value.min, $lte: value.max },
|
|
354
384
|
}),
|
|
355
385
|
},
|
|
356
|
-
|
|
386
|
+
// Only 'search' and 'priceRange' filters are allowed from client
|
|
357
387
|
```
|
|
358
388
|
|
|
359
389
|
#### Understanding `filterCustom` Flow
|
|
360
390
|
|
|
361
391
|
To use custom filters effectively, you must match the **Key** on the client with the **Key** on the server.
|
|
362
392
|
|
|
363
|
-
1.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
393
|
+
1. **Client-side**: Define a filter with a specific `id` (e.g., `'priceRange'`).
|
|
394
|
+
|
|
395
|
+
```tsx
|
|
396
|
+
// Client Component
|
|
397
|
+
<DataKitTable
|
|
398
|
+
filters={[{ id: 'priceRange', label: 'Price Range', type: 'TEXT' }]}
|
|
399
|
+
// ...
|
|
400
|
+
/>
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
_Note: When use interact with this filter, `DataKit` sends `{ filter: { priceRange: "value" } }` to the server._
|
|
404
|
+
|
|
405
|
+
2. **Server-side**: Handle that key in `filterCustom`.
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
// Server Action
|
|
409
|
+
filterCustom: {
|
|
410
|
+
// MATCHES 'priceRange' FROM CLIENT
|
|
411
|
+
priceRange: value => ({
|
|
412
|
+
price: { $lte: Number(value) },
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
The `filterCustom` function intercepts the value sent from the client before it hits the database query builder, allowing you to transform simple values into complex queries.
|
|
388
418
|
|
|
389
419
|
**Client Usage:**
|
|
390
420
|
|
|
@@ -396,10 +426,10 @@ const {
|
|
|
396
426
|
});
|
|
397
427
|
|
|
398
428
|
// Trigger the manual search
|
|
399
|
-
setFilter(
|
|
429
|
+
setFilter('search', 'query string');
|
|
400
430
|
|
|
401
431
|
// Trigger the range filter
|
|
402
|
-
setFilter(
|
|
432
|
+
setFilter('priceRange', { min: 10, max: 100 });
|
|
403
433
|
```
|
|
404
434
|
|
|
405
435
|
### Client
|
|
@@ -408,29 +438,29 @@ setFilter("priceRange", { min: 10, max: 100 });
|
|
|
408
438
|
|
|
409
439
|
Full-featured table component with built-in UI.
|
|
410
440
|
|
|
411
|
-
| Prop
|
|
412
|
-
|
|
413
|
-
| `action`
|
|
414
|
-
| `table`
|
|
415
|
-
| `filters`
|
|
416
|
-
| `selectable`
|
|
417
|
-
| `limit`
|
|
418
|
-
| `controller`
|
|
419
|
-
| `className`
|
|
420
|
-
| `bordered`
|
|
421
|
-
| `refetchInterval` | `number`
|
|
441
|
+
| Prop | Type | Description |
|
|
442
|
+
| ----------------- | ---------------------------- | -------------------------- |
|
|
443
|
+
| `action` | `(input) => Promise<Result>` | Server action function |
|
|
444
|
+
| `table` | `Column[]` | Column definitions |
|
|
445
|
+
| `filters` | `FilterItem[]` | Filter configurations |
|
|
446
|
+
| `selectable` | `{ enabled, actions? }` | Selection & bulk actions |
|
|
447
|
+
| `limit` | `{ default: number }` | Items per page |
|
|
448
|
+
| `controller` | `Ref<Controller>` | External control ref |
|
|
449
|
+
| `className` | `string` | Container class |
|
|
450
|
+
| `bordered` | `boolean \| 'rounded'` | Border style |
|
|
451
|
+
| `refetchInterval` | `number` | Auto-refresh interval (ms) |
|
|
422
452
|
|
|
423
453
|
#### `<DataKit>` Component
|
|
424
454
|
|
|
425
455
|
Headless component for custom layouts (grids, cards, etc).
|
|
426
456
|
|
|
427
|
-
| Prop
|
|
428
|
-
|
|
429
|
-
| `action`
|
|
430
|
-
| `filters`
|
|
431
|
-
| `limit`
|
|
432
|
-
| `manual`
|
|
433
|
-
| `children` | `(dataKit) => ReactNode`
|
|
457
|
+
| Prop | Type | Description |
|
|
458
|
+
| ---------- | ---------------------------- | --------------------------------- |
|
|
459
|
+
| `action` | `(input) => Promise<Result>` | Server action function |
|
|
460
|
+
| `filters` | `FilterItem[]` | Filter configurations |
|
|
461
|
+
| `limit` | `{ default: number }` | Items per page |
|
|
462
|
+
| `manual` | `boolean` | Skip loading/empty state handling |
|
|
463
|
+
| `children` | `(dataKit) => ReactNode` | Render function |
|
|
434
464
|
|
|
435
465
|
#### `useDataKit(options)`
|
|
436
466
|
|
|
@@ -455,23 +485,23 @@ interface TDataKitControllerOptions<T, R> {
|
|
|
455
485
|
|
|
456
486
|
Returns:
|
|
457
487
|
|
|
458
|
-
-
|
|
459
|
-
-
|
|
460
|
-
-
|
|
461
|
-
-
|
|
462
|
-
-
|
|
463
|
-
-
|
|
464
|
-
-
|
|
465
|
-
-
|
|
466
|
-
-
|
|
467
|
-
-
|
|
468
|
-
-
|
|
469
|
-
-
|
|
470
|
-
-
|
|
471
|
-
-
|
|
472
|
-
-
|
|
473
|
-
-
|
|
474
|
-
-
|
|
488
|
+
- `items` - Current page items
|
|
489
|
+
- `page` - Current page number
|
|
490
|
+
- `limit` - Items per page
|
|
491
|
+
- `total` - Total document count
|
|
492
|
+
- `sorts` - Current sort configuration
|
|
493
|
+
- `filter` - Current filter values
|
|
494
|
+
- `state`
|
|
495
|
+
- `isLoading` - Loading state
|
|
496
|
+
- `error` - Error state
|
|
497
|
+
- `actions`
|
|
498
|
+
- `setPage(page)` - Go to a specific page
|
|
499
|
+
- `setLimit(limit)` - Set items per page
|
|
500
|
+
- `setSort(path, value)` - Set sort for a column
|
|
501
|
+
- `setFilter(key, value)` - Set a filter value
|
|
502
|
+
- `clearFilters()` - Clear all filters
|
|
503
|
+
- `refresh()` - Refresh the table data
|
|
504
|
+
- `reset()` - Reset to initial state
|
|
475
505
|
|
|
476
506
|
#### `useSelection<T>()`
|
|
477
507
|
|
|
@@ -512,7 +542,7 @@ const { pages, hasNextPage, hasPrevPage, totalPages } = usePagination({
|
|
|
512
542
|
|
|
513
543
|
```typescript
|
|
514
544
|
interface TDataKitInput<T = unknown> {
|
|
515
|
-
action?:
|
|
545
|
+
action?: 'FETCH';
|
|
516
546
|
page?: number;
|
|
517
547
|
limit?: number;
|
|
518
548
|
sort?: TSortOptions<T>;
|
|
@@ -527,7 +557,7 @@ interface TDataKitInput<T = unknown> {
|
|
|
527
557
|
|
|
528
558
|
```typescript
|
|
529
559
|
interface TDataKitResult<R> {
|
|
530
|
-
type:
|
|
560
|
+
type: 'ITEMS';
|
|
531
561
|
items: R[];
|
|
532
562
|
documentTotal: number;
|
|
533
563
|
}
|
|
@@ -538,7 +568,7 @@ interface TDataKitResult<R> {
|
|
|
538
568
|
```typescript
|
|
539
569
|
interface TFilterConfig {
|
|
540
570
|
[key: string]: {
|
|
541
|
-
type:
|
|
571
|
+
type: 'REGEX' | 'EXACT';
|
|
542
572
|
field?: string;
|
|
543
573
|
};
|
|
544
574
|
}
|
|
@@ -549,7 +579,7 @@ interface TFilterConfig {
|
|
|
549
579
|
The package provides generic database types that work with any ORM/ODM:
|
|
550
580
|
|
|
551
581
|
```typescript
|
|
552
|
-
import type { TModel, TMongoFilterQuery, TQueryBuilder } from
|
|
582
|
+
import type { TModel, TMongoFilterQuery, TQueryBuilder } from 'next-data-kit/types';
|
|
553
583
|
|
|
554
584
|
// Your model just needs to implement the Model interface
|
|
555
585
|
interface MyModel extends TModel<MyDocument> {
|
|
@@ -566,8 +596,8 @@ MIT © muhgholy
|
|
|
566
596
|
|
|
567
597
|
This repo includes a **dev-only** playground you can open in the browser to preview components and validate end-to-end behavior against a **temporary in-memory MongoDB**.
|
|
568
598
|
|
|
569
|
-
-
|
|
570
|
-
-
|
|
599
|
+
- It is **not exported** in `package.json#exports`.
|
|
600
|
+
- It is **not published** to npm because `package.json#files` only includes `dist`.
|
|
571
601
|
|
|
572
602
|
### Run
|
|
573
603
|
|
|
@@ -577,8 +607,8 @@ npm run playground:dev
|
|
|
577
607
|
|
|
578
608
|
Then open:
|
|
579
609
|
|
|
580
|
-
-
|
|
581
|
-
-
|
|
610
|
+
- Web UI: http://localhost:5173
|
|
611
|
+
- API health: http://127.0.0.1:8787/api/health
|
|
582
612
|
|
|
583
613
|
### Reset seed data
|
|
584
614
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDataKit.d.ts","sourceRoot":"","sources":["../../../src/client/hooks/useDataKit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"useDataKit.d.ts","sourceRoot":"","sources":["../../../src/client/hooks/useDataKit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAiC,yBAAyB,EAAc,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAO3H,eAAO,MAAM,UAAU,GAAI,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,OAAO,QAAQ,CAAC,yBAAyB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAG,iBAAiB,CAAC,CAAC,EAAE,CAAC,CA2Q7H,CAAC;AAEF,YAAY,EAAE,yBAAyB,EAAE,CAAC"}
|