Define your search, sort, filter, and pagination rules once. Run them anywhere. Get back everything your UI needs in one call.
Zero dependencies. Works with any JavaScript environment — no adapter, no build config required.
npm install @datafluxgrid/fluxgrid-data
<script src="https://cdn.jsdelivr.net/npm/@datafluxgrid/fluxgrid-data/dist/index.js"></script> <script> const q = createQuery({ search: ['name'], pageSize: 10 }); </script>
Without fluxgrid-data
const filtered = users .filter(u => ['name','email'] .some(f => u[f].toLowerCase() .includes(term))) .sort((a,b) => a.name.localeCompare(b.name)); const total = filtered.length; const pages = Math.ceil(total/10); const data = filtered.slice( (page-1)*10, page*10); const hasNext = page < pages; const hasPrev = page > 1; const from = (page-1)*10+1; const to = Math.min(page*10,total); // isEmpty, isNoResults, allSelected, // someSelected, ms — still missing
With fluxgrid-data
import { createQuery } from '@datafluxgrid/fluxgrid-data'; const userQuery = createQuery({ search: ['name', 'email'], sort: { field: 'name' }, pageSize: 10, }); const result = userQuery.run( users, { term, page } ); // result has everything ↓
{
data: [
{ id: 1, name: "Arjun Sharma", role: "Admin", salary: 95000 },
{ id: 2, name: "Priya Nair", role: "Editor", salary: 72000 },
{ id: 3, name: "Rahul Mehta", role: "Viewer", salary: 61000 },
{ id: 4, name: "Sneha Patel", role: "Editor", salary: 78000 },
{ id: 5, name: "Vikram Singh", role: "Admin", salary: 102000 },
{ id: 6, name: "Divya Reddy", role: "Viewer", salary: 58000 },
{ id: 7, name: "Karthik Iyer", role: "Editor", salary: 85000 },
{ id: 8, name: "Meera Nair", role: "Viewer", salary: 55000 },
{ id: 9, name: "Aditya Kumar", role: "Admin", salary: 110000 },
{ id: 10, name: "Lakshmi Rao", role: "Editor", salary: 74000 },
],
total: 47, // total matching records across all pages
page: 1, // current page number
pages: 5, // total pages (47 records / 10 per page)
pageSize: 10, // rows per page
from: 1, // first record on this page
to: 10, // last record — use for "Showing 1–10 of 47"
hasNext: true, // page 1 of 5 — next page exists
hasPrev: false, // page 1 — no previous page
term: "a", // active search term — use to highlight matched text
sortField: "name", // active sort column — show arrow indicator on header
sortDir: "asc", // sort direction — A → Z
isEmpty: false, // false — data exists, no empty state needed
isNoResults: false, // false — search returned results
allSelected: false, // not all 10 rows on this page are checked
someSelected: true, // rows 1 and 3 checked — show indeterminate checkbox
ms: 1, // entire query ran in 1ms
}
The same createQuery API handles every common frontend data pattern.
import { useState } from 'react'; import { createQuery } from '@datafluxgrid/fluxgrid-data'; const userQuery = createQuery({ search: ['name', 'email', 'department'], sort: { field: 'name', dir: 'asc' }, pageSize: 10, }); export function UsersTable({ users }) { const [term, setTerm] = useState(''); const [page, setPage] = useState(1); const [sortField, setSortField] = useState('name'); const [sortDir, setSortDir] = useState('asc'); const [roleFilter, setRole] = useState(''); const [selected, setSelected] = useState([]); const result = userQuery.run(users, { term, page, sortField, sortDir, selected, filterFn: u => !roleFilter || u.role === roleFilter, }); return ( <> <input value={term} onChange={e => { setTerm(e.target.value); setPage(1); }} /> {result.isEmpty && <p>No users yet.</p>} {result.isNoResults && <p>No results for "{result.term}"</p>} <p>Showing {result.from}–{result.to} of {result.total}</p> {result.data.map(user => ( <tr key={user.id}><td>{user.name}</td></tr> ))} <button disabled={!result.hasPrev} onClick={() => setPage(p => p - 1)}>Prev</button> <button disabled={!result.hasNext} onClick={() => setPage(p => p + 1)}>Next</button> </> ); }
import { createQuery } from '@datafluxgrid/fluxgrid-data'; interface User { id: number; name: string; role: 'Admin' | 'Editor' | 'Viewer'; department: string; salary: number; } const userQuery = createQuery<User>({ search: ['name', 'department'], sort: { field: 'salary', dir: 'desc' }, pageSize: 5, }); const result = userQuery.run(users, { term, page }); // result.data → typed as User[] // result.total → typed as number // result.hasNext → typed as boolean
const taskQuery = createQuery({ search: ['title', 'assignee'], groupBy: 'status', sort: { field: 'priority', dir: 'asc' }, }); const { groups, total } = taskQuery.run(tasks, { term }); // groups = { // 'Todo': [{ title: 'Design page', ... }], // 'In Progress': [{ title: 'Fix bug', ... }], // 'Done': [{ title: 'Write docs', ... }], // } Object.entries(groups).map(([status, items]) => ( <section key={status}> <h3>{status} ({items.length})</h3> {items.map(task => <TaskCard task={task} />)} </section> ))
const saved = userQuery.toJSON(); localStorage.setItem('myQuery', JSON.stringify(saved)); const restored = createQuery.fromJSON( JSON.parse(localStorage.getItem('myQuery')) ); // toJSON() produces: // { // search: ['name', 'email'], // sort: { field: 'name', dir: 'asc' }, // pageSize: 10 // } // Use cases: // — Save user filter view between sessions // — Share filtered table via URL param // — Send pipeline to server for execution
<script setup> import { ref, computed } from 'vue'; import { createQuery } from '@datafluxgrid/fluxgrid-data'; const props = defineProps(['users']); const term = ref(''); const page = ref(1); const userQuery = createQuery({ search: ['name', 'role'], sort: { field: 'name' }, pageSize: 10, }); const result = computed(() => userQuery.run(props.users, { term: term.value, page: page.value }) ); </script> <template> <input v-model="term" @input="page = 1" /> <p>{{ result.from }}–{{ result.to }} of {{ result.total }}</p> <button :disabled="!result.hasPrev" @click="page--">Prev</button> <button :disabled="!result.hasNext" @click="page++">Next</button> </template>
Two functions. One config object. One options object.
createQuery(config)
| Option | Type | Description |
|---|---|---|
| search | string[] | Fields to search across when term is provided |
| sort | { field, dir } | Default sort field and direction. dir is 'asc' or 'desc' |
| pageSize | number | Records per page. Default is 10 |
| filter | (item) => boolean | Static filter applied on every .run() call |
| groupBy | string | Group records by this field. Changes result shape to { groups, total, ms } |
query.run(data, options)
| Option | Type | Description |
|---|---|---|
| term | string | Search term. Empty string means no filter — show all |
| page | number | Current page number. Starts at 1 |
| pageSize | number | Override pageSize from config for this run |
| sortField | string | Override sort field from config for this run |
| sortDir | 'asc' | 'desc' | Override sort direction for this run |
| filterFn | (item) => boolean | Dynamic filter — for dropdowns, sliders, toggles |
| selected | (string | number)[] | Selected row IDs for allSelected / someSelected helpers |
Result object
| Field | Type | Description |
|---|---|---|
| data | T[] | Records for the current page — render this |
| total | number | Total matching records across all pages |
| page | number | Current page number |
| pages | number | Total number of pages |
| pageSize | number | Records per page |
| from | number | First record number — for "Showing 21–30 of 247" |
| to | number | Last record number |
| hasNext | boolean | True if there is a next page |
| hasPrev | boolean | True if there is a previous page |
| term | string | Active search term — use for highlighting matched text |
| sortField | string | Active sort field — show sort arrow on column header |
| sortDir | string | Active sort direction — 'asc' or 'desc' |
| isEmpty | boolean | No data and no term — show empty state illustration |
| isNoResults | boolean | Search returned nothing — show "no results" message |
| allSelected | boolean | All rows on current page are selected |
| someSelected | boolean | Some rows selected — for indeterminate checkbox state |
| ms | number | Query execution time in milliseconds |
Stop copy-pasting filter logic. One package covers search, sort, paginate, group, and gives you full UI meta in every result.
| Feature | fluxgrid-data | @tanstack/react-table | fuse.js | match-sorter | datapipe-js |
|---|---|---|---|---|---|
| Search across fields | Yes | Yes | Yes | Yes | Yes |
| Sort | Yes | Yes | No | Yes | Yes |
| Paginate | Yes | Yes | No | No | No |
| GroupBy | Yes | Yes | No | No | Yes |
| Full meta output (total, from, to, hasNext…) | Yes | No — manual | No | No | No |
| isEmpty / isNoResults states | Yes | No | No | No | No |
| Checkbox helpers (allSelected / someSelected) | Yes | No | No | No | No |
| Serializable toJSON() / fromJSON() | Yes | No | No | No | No |
| Framework agnostic | Yes | No — adapter | Yes | Yes | Yes |
| No columns config required | Yes | No — mandatory | Yes | Yes | Yes |
| Zero dependencies | Yes | Yes | Yes | No | No |
| TypeScript support | Yes | Yes | Yes | Yes | Partial |
| CDN script tag | Yes | No | Yes | No | No |
| Actively maintained | Yes | Yes | Yes | Yes | No |
| Bundle size (minzipped) | ~1 kB | ~15–20 kB | ~5 kB | ~3 kB | ~8 kB |
| Setup lines of code | ~5 | ~40–60 | ~10 | ~5 | ~10 |
No plugin system. No config objects inside config objects. Every feature works out of the box.
Multi-field search
Search across any fields. Case insensitive. Partial match. Active term returned for text highlighting.
Sort any field
String and number sorting handled automatically. Override field and direction per run.
Smart pagination
from, to, hasNext, hasPrev, pages all returned automatically. Out-of-bounds pages clamped safely.
GroupBy
Group records by any field. Result changes to groups object. Perfect for Kanban and analytics.
Serializable pipelines
toJSON() and fromJSON() let you save, share, and restore query definitions. No other utility does this.
Selection helpers
Pass selected IDs and get allSelected and someSelected back. Indeterminate checkbox handled for you.