@insurup/table-adapter-core
v0.1.19
Published
Framework-agnostic TanStack Table adapter for InsurUp SDK with @tanstack/query-core
Maintainers
Readme
@insurup/table-adapter-core
Framework-agnostic TanStack Table adapter for @insurup/sdk.
This is the core package that provides the table adapter logic. For framework-specific bindings, see:
@insurup/table-adapter-react- React bindings@insurup/table-adapter-vue- Vue bindings@insurup/table-adapter-svelte- Svelte bindings
Features
- 🚀 Framework agnostic - works with React, Vue, Svelte, Solid, or vanilla JS
- 🔄 Built-in caching via
@tanstack/query-core - 📝 Type-safe columns with SDK field autocompletion
- 📄 Cursor pagination handled internally
Installation
npm install @insurup/table-adapter-core @insurup/sdkbun add @insurup/table-adapter-core @insurup/sdkQuick Start
import { createCustomerTable } from '@insurup/table-adapter-core';
import { DefaultInsurUpClient } from '@insurup/sdk';
const client = new DefaultInsurUpClient({
baseUrl: 'https://api.insurup.com',
tokenProvider: () => token,
});
// Create table adapter with builder API
const customerTable = createCustomerTable({
columns: (col) => [
col.id({ header: 'ID' }),
col.name({ header: 'Name', sortable: true }),
col.primaryEmail('Email'),
col.createdAt({ header: 'Created', sortable: true }),
],
fetch: (options) => client.customers.getCustomers(options),
pageSize: 10,
autoFetch: true,
// Sorting is managed by the consumer via tableOptions.state.sorting
tableOptions: {
state: {
sorting: [{ id: 'createdAt', desc: true }],
},
onSortingChange: (updater) => {
// Update your sorting state here
},
},
});
// Subscribe to state changes
const unsubscribe = customerTable.subscribe(() => {
const state = customerTable.getSnapshot();
console.log('Data:', state.rows);
});
// Get TanStack Table options
const tableOptions = customerTable.getTableOptions();
// Cleanup when done
customerTable.destroy();API
createCustomerTable
Creates a customer table adapter with built-in caching and state management.
const customerTable = createCustomerTable({
// Required
columns: (col) => [...], // Builder function for columns
fetch: (options) => Promise, // SDK fetch function
// Optional
pageSize?: number, // Items per page (default: 20)
defaultFilter?: UnifiedFilterInput, // Initial filter (see "Filter and search" below)
staleTime?: number, // Cache stale time in ms (default: 30000)
gcTime?: number, // Garbage collection time in ms (default: 300000)
autoFetch?: boolean, // Fetch on creation (default: false)
// Callbacks
onError?: (error) => void, // Called on fetch error
onSuccess?: (data) => void, // Called on fetch success
onSettled?: (data, error) => void // Called after fetch completes
});Filter and search
The adapter exposes a single unified filter API. There is no separate setSearch — every field in setFilter is either a regular filter or, when carrying the $search: true marker, a server-side search clause.
table.setFilter({
// Regular filter — routed to the server's `filter:` slot
type: { eq: CustomerType.Individual },
birthDate: { gte: '1990-01-01' },
// Search — `$search: true` promotes the field to the server's `search:` slot
// and unlocks the search-only operators (`textSearch`, `wildcard`, `autocomplete`)
name: { $search: true, textSearch: 'ali' },
// Long form with relevance score
identityNumber: {
$search: true,
textSearch: { value: '123', score: { boost: 2 } },
},
});String shorthand is normalized by the SDK: textSearch: 'ali' is equivalent to textSearch: { value: 'ali' }. Likewise in: ['a','b'] becomes { values: ['a','b'] }.
and / or combinators are supported recursively. Each combinator item is split per-bucket — and semantics are preserved; an or whose items mix filter and search keys becomes an implicit AND at the wire (server combines its filter: and search: slots with AND). Keep each or item homogeneous if intent matters.
To introspect what's filterable / searchable on an entity at runtime, read the generated meta:
import { QueryCustomerModelMeta } from '@insurup/sdk';
QueryCustomerModelMeta.name.filterable; // true
QueryCustomerModelMeta.name.searchable; // true
QueryCustomerModelMeta.name.filterOperators; // ['eq','neq','contains','notContains', ...]
QueryCustomerModelMeta.name.searchOperators; // ['eq','neq','textSearch','wildcard', ...]Column Builder
The columns option receives a builder with methods for each field:
columns: (col) => [
// Simple - uses field name as header
col.id(),
// With custom header
col.name('Customer Name'),
// With full config
col.type({
header: 'Type',
sortable: true,
render: (value) => (value === 'Individual' ? '👤' : '🏢'),
}),
// Computed column using multiple fields
col.computed({
uses: ['cityText', 'districtText'] as const,
header: 'Location',
render: (row) => `${row.cityText}, ${row.districtText}`,
}),
];Adapter Methods
| Method | Description |
| --------------------- | ------------------------------------------------------------------------- |
| columns | TanStack ColumnDef[] (readonly) |
| getState() | Current state: { rows, rowCount, pageCount, isLoading, error, ... } |
| getTableOptions() | Complete TanStack Table options (includes data, columns, getCoreRowModel) |
| subscribe(listener) | Subscribe to state changes (for useSyncExternalStore) |
| getSnapshot() | Get current state snapshot (for useSyncExternalStore) |
| getServerSnapshot() | Get server snapshot for SSR (for useSyncExternalStore) |
| fetch() | Trigger a manual fetch |
| invalidate() | Invalidate cache and refetch |
| refetch({ force }) | Refetch with optional cache bypass |
| destroy() | Clean up resources (call on unmount) |
| setFilter(filter) | Set unified filter (filter + $search-marked entries) and refetch |
| getFilter() | Return the current unified filter |
| clearFilter() | Clear filter and refetch |
| setPageSize(size) | Change page size |
AdapterState
State returned by getState() and getSnapshot().
interface AdapterState<TEntity> {
rows: TEntity[]; // Current page data
rowCount: number; // Total records
pageCount: number; // Total pages
isLoading: boolean; // Initial loading
isFetching: boolean; // Any fetch in progress
error: Error | null; // Error if any
isError: boolean;
isSuccess: boolean;
}Pagination Limitations
This adapter uses cursor-based pagination, which only supports sequential navigation (previous/next). Jumping to arbitrary pages (e.g., page 1 → page 5) is not supported.
The getTableOptions() method returns paginationMode: 'cursor' to signal this limitation.
License
MIT
