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 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
- - 🚀 **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
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
- "use server";
31
+ 'use server';
32
32
 
33
- import { dataKitServerAction, createSearchFilter } from "next-data-kit/server";
34
- import type { TDataKitInput } from "next-data-kit/types";
35
- import UserModel from "@/models/User";
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 (user) => ({
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(["name", "email"]),
51
- age: (value) => ({
52
- age: {
53
- $gte: value,
54
- }
55
- })
50
+ search: createSearchFilter(['name', 'email']),
51
+ age: value => ({
52
+ age: {
53
+ $gte: value,
54
+ },
55
+ }),
56
56
  },
57
- filterAllowed: ["search", "age"],
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
- "use server";
67
+ 'use server';
69
68
 
70
- import { dataKitServerAction, dataKitSchemaZod } from "next-data-kit/server";
69
+ import { dataKitServerAction, dataKitSchemaZod } from 'next-data-kit/server';
71
70
 
72
71
  export async function fetchUsers(input: unknown) {
73
- // Validate input
74
- const parsedInput = dataKitSchemaZod.parse(input);
75
-
76
- return dataKitServerAction({
77
- model: UserModel,
78
- input: parsedInput,
79
- item: (user) => ({ id: user._id.toString(), name: user.name }),
80
- filterAllowed: ["search", "role"],
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
- "use client";
93
+ 'use client';
91
94
 
92
- import { DataKitTable } from "next-data-kit/client";
93
- import { fetchUsers } from "@/actions/users";
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: "search", label: "Search", type: "TEXT", placeholder: "Search..." },
104
+ { id: 'search', label: 'Search', type: 'TEXT', placeholder: 'Search...' },
102
105
  {
103
- id: "role",
104
- label: "Role",
105
- type: "SELECT",
106
+ id: 'role',
107
+ label: 'Role',
108
+ type: 'SELECT',
106
109
  dataset: [
107
- { id: "admin", name: "admin", label: "Admin" },
108
- { id: "user", name: "user", label: "User" },
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: "Delete Selected",
117
- function: async (items) => {
118
- await deleteUsers(items.map((i) => i.id));
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: "name", default: 0 },
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
- "use client";
148
+ 'use client';
146
149
 
147
- import { DataKit } from "next-data-kit/client";
148
- import { fetchUsers } from "@/actions/users";
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
- action={fetchUsers}
154
- limit={{ default: 12 }}
155
- filters={[{ id: "search", label: "Search", type: "TEXT" }]}
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
- {(dataKit) => (
175
+ {dataKit => (
177
176
  <>
178
177
  {dataKit.state.isLoading && <Spinner />}
179
- {dataKit.items.map((user) => <Card key={user.id} user={user} />)}
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
- "use client";
191
+ 'use client';
191
192
 
192
- import { useDataKit } from "next-data-kit/client";
193
- import { fetchUsers } from "@/actions/users";
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="Search..." onChange={(e) => setFilter("search", e.target.value)} />
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("name", 1)}>Name</th>
220
- <th onClick={() => setSort("email", 1)}>Email</th>
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((user) => (
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
- // ** Whitelist of allowed filter fields (crucial for security when using query prop)
261
- filterAllowed?: string[];
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
- ### Security Note: `queryAllowed` & `filterAllowed` (Strict Mode)
276
+ ### Understanding `filter` vs `query`
272
277
 
273
- Security is strict by default when whitelists are provided. If you provide `filterAllowed` or `queryAllowed`, any input field **NOT** in the whitelist will cause the server action to **THROW AN ERROR**. This ensures that no hidden or unauthorized fields can be filtered or queried.
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
- model: UserModel,
279
- input,
280
- item: (u) => ({ id: u._id.toString(), name: u.name }),
281
- // If client sends { filter: { secret: "true" } }, this WILL THROW an Error!
282
- filterAllowed: ["name", "email", "role"],
283
- // Same for query params
284
- queryAllowed: ["status"],
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 Error (e.g., due to a security violation), `next-data-kit` catches it on the client side.
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
- - **`DataKitTable`**: Automatically displays the error message in Red within the table body.
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
- // Custom UI with useDataKit
297
- const { state: { error } } = useDataKit({ ... });
340
+ const { state: { error } } = useDataKit({ action: fetchUsers });
298
341
 
299
342
  if (error) {
300
- return <div className="text-red-500">Error: {error.message}</div>;
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(["name", "email", "phone"]);
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
- filterAllowed: ["search", "priceRange"]
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. **Client-side**: Define a filter with a specific `id` (e.g., `'priceRange'`).
364
- ```tsx
365
- // Client Component
366
- <DataKitTable
367
- filters={[
368
- { id: 'priceRange', label: 'Price Range', type: 'TEXT' }
369
- ]}
370
- // ...
371
- />
372
- ```
373
-
374
- _Note: When use interact with this filter, `DataKit` sends `{ filter: { priceRange: "value" } }` to the server._
375
-
376
- 2. **Server-side**: Handle that key in `filterCustom`.
377
- ```typescript
378
- // Server Action
379
- filterCustom: {
380
- // MATCHES 'priceRange' FROM CLIENT
381
- priceRange: (value) => ({
382
- price: { $lte: Number(value) }
383
- })
384
- }
385
- ```
386
-
387
- 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.
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("search", "query string");
429
+ setFilter('search', 'query string');
400
430
 
401
431
  // Trigger the range filter
402
- setFilter("priceRange", { min: 10, max: 100 });
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 | Type | Description |
412
- |------|------|-------------|
413
- | `action` | `(input) => Promise<Result>` | Server action function |
414
- | `table` | `Column[]` | Column definitions |
415
- | `filters` | `FilterItem[]` | Filter configurations |
416
- | `selectable` | `{ enabled, actions? }` | Selection & bulk actions |
417
- | `limit` | `{ default: number }` | Items per page |
418
- | `controller` | `Ref<Controller>` | External control ref |
419
- | `className` | `string` | Container class |
420
- | `bordered` | `boolean \| 'rounded'` | Border style |
421
- | `refetchInterval` | `number` | Auto-refresh interval (ms) |
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 | Type | Description |
428
- |------|------|-------------|
429
- | `action` | `(input) => Promise<Result>` | Server action function |
430
- | `filters` | `FilterItem[]` | Filter configurations |
431
- | `limit` | `{ default: number }` | Items per page |
432
- | `manual` | `boolean` | Skip loading/empty state handling |
433
- | `children` | `(dataKit) => ReactNode` | Render function |
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
- - `items` - Current page items
459
- - `page` - Current page number
460
- - `limit` - Items per page
461
- - `total` - Total document count
462
- - `sorts` - Current sort configuration
463
- - `filter` - Current filter values
464
- - `state`
465
- - `isLoading` - Loading state
466
- - `error` - Error state
467
- - `actions`
468
- - `setPage(page)` - Go to a specific page
469
- - `setLimit(limit)` - Set items per page
470
- - `setSort(path, value)` - Set sort for a column
471
- - `setFilter(key, value)` - Set a filter value
472
- - `clearFilters()` - Clear all filters
473
- - `refresh()` - Refresh the table data
474
- - `reset()` - Reset to initial state
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?: "FETCH";
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: "ITEMS";
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: "REGEX" | "EXACT";
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 "next-data-kit/types";
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
- - It is **not exported** in `package.json#exports`.
570
- - It is **not published** to npm because `package.json#files` only includes `dist`.
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
- - Web UI: http://localhost:5173
581
- - API health: http://127.0.0.1:8787/api/health
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,EAGP,yBAAyB,EAEzB,iBAAiB,EACrB,MAAM,aAAa,CAAC;AAOrB,eAAO,MAAM,UAAU,GAAI,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAC9C,OAAO,QAAQ,CAAC,yBAAyB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAClD,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAwRxB,CAAC;AAEF,YAAY,EAAE,yBAAyB,EAAE,CAAC"}
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"}