Typing APIs with feature maps
Posted on Mon 28 September 2026 in Tech, TypeScript
I was very intrigued by the latest release of TanStack Table, in particular the feature dependent type-safe API, in a nutshell it looks like this:
const const features: {
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}
features = tableFeatures<{
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}>(features: {
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
} & {}): {
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}
A helper function to help define the features that are to be imported and applied to a table instance.
Use this utility to make it easier to have the correct type inference for the features that are being imported.
Note: It is recommended to use this utility statically outside of a component.
Alongside feature modules, this object carries everything else that is
statically stitched into the table:
- Row model factories (
sortedRowModel, filteredRowModel, etc.)
- Row model function registries (
sortFns, filterFns, aggregationFns),
whose keys become the valid string values for sortFn, filterFn,
globalFilterFn, and aggregationFn with full inference
- Type-only
tableMeta/columnMeta slots for declaring per-table meta types
instead of using global declaration merging. The values are phantom
(ignored and stripped at runtime); only their types are used.
tableFeatures({
coreReactivityFeature: TableReactivityBindingscoreReactivityFeature: function storeReactivityBindings(): TableReactivityBindingsTanStack Store–based reactivity for vanilla / non-framework use of constructTable,
with createOptionsStore: true so table.optionsStore is available for subscriptions.
storeReactivityBindings(),
rowSortingFeature: TableFeaturerowSortingFeature,
})
const const basic: Table<{
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}, {
name: string;
}>
basic = constructTable<{
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}, {
name: string;
}>(tableOptions: TableOptions<{
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}, {
name: string;
}>): Table<{
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}, {
name: string;
}>
Constructs a table instance from normalized table internals.
This wires core properties, feature prototype APIs, and instance data used by table rendering and row-model operations.
constructTable({ TableOptions_Table<{ coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }>.features: {
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
} & {}
The feature modules registered on this table instance.
Feature registration controls which state slices, options, and prototype
APIs are available. This object also carries the table's row model
factories (sortedRowModel, filteredRowModel, etc.), row model function
registries (sortFns, filterFns, aggregationFns), and type-only meta
slots (tableMeta, columnMeta).
features, TableOptions_Table<{ coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }>.data: readonly {
name: string;
}[]
The data for the table to display. When the data option changes reference, the table will reprocess the data.
data, TableOptions_Columns<{ coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }, unknown>.columns: readonly ColumnDef<{
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}, {
name: string;
}, unknown>[]
The array of column defs to use for the table.
columns })
const const enhanced: Table<{
rowSelectionFeature: TableFeature;
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}, {
name: string;
}>
enhanced = constructTable<{
rowSelectionFeature: TableFeature;
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}, {
name: string;
}>(tableOptions: TableOptions<{
rowSelectionFeature: TableFeature;
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}, {
name: string;
}>): Table<{
rowSelectionFeature: TableFeature;
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}, {
name: string;
}>
Constructs a table instance from normalized table internals.
This wires core properties, feature prototype APIs, and instance data used by table rendering and row-model operations.
constructTable({
TableOptions_Table<{ rowSelectionFeature: TableFeature; coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }>.features: {
rowSelectionFeature: TableFeature;
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
} & {}
The feature modules registered on this table instance.
Feature registration controls which state slices, options, and prototype
APIs are available. This object also carries the table's row model
factories (sortedRowModel, filteredRowModel, etc.), row model function
registries (sortFns, filterFns, aggregationFns), and type-only meta
slots (tableMeta, columnMeta).
features: tableFeatures<{
rowSelectionFeature: TableFeature;
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}>(features: {
rowSelectionFeature: TableFeature;
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
} & {}): {
rowSelectionFeature: TableFeature;
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}
A helper function to help define the features that are to be imported and applied to a table instance.
Use this utility to make it easier to have the correct type inference for the features that are being imported.
Note: It is recommended to use this utility statically outside of a component.
Alongside feature modules, this object carries everything else that is
statically stitched into the table:
- Row model factories (
sortedRowModel, filteredRowModel, etc.)
- Row model function registries (
sortFns, filterFns, aggregationFns),
whose keys become the valid string values for sortFn, filterFn,
globalFilterFn, and aggregationFn with full inference
- Type-only
tableMeta/columnMeta slots for declaring per-table meta types
instead of using global declaration merging. The values are phantom
(ignored and stripped at runtime); only their types are used.
tableFeatures({ ...const features: {
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}
features, rowSelectionFeature: TableFeaturerowSelectionFeature }),
TableOptions_Table<{ rowSelectionFeature: TableFeature; coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }>.data: readonly {
name: string;
}[]
The data for the table to display. When the data option changes reference, the table will reprocess the data.
data,
TableOptions_Columns<{ rowSelectionFeature: TableFeature; coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }, unknown>.columns: readonly ColumnDef<{
rowSelectionFeature: TableFeature;
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}, {
name: string;
}, unknown>[]
The array of column defs to use for the table.
columns,
})
const enhanced: Table<{
rowSelectionFeature: TableFeature;
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}, {
name: string;
}>
enhanced.Table_RowSelection<{ rowSelectionFeature: TableFeature; coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }>.toggleAllRowsSelected: (value?: boolean, opts?: {
deselectAll?: boolean;
}) => void
Selects/deselects all rows in the table.
Deselecting keeps rows that cannot be selected in the selection map unless
opts.deselectAll is true.
toggleAllRowsSelected(true)
// this method does not exist on the basic version (missing rowSelectionFeature)
const basic: Table<{
coreReactivityFeature: TableReactivityBindings;
rowSortingFeature: TableFeature;
}, {
name: string;
}>
basic.toggleAllRowsSelected
Properties are available based on the features that were registered on the object.
TanStack already published some very good articles here and here on how this works, but I would like to understand how it was really implemented, and how I could use this in my own APIs. In this article we will build a (very simple) editor using the same technique (this will also make the code easier to read as we won't need to carry TData everywhere).
First let's define what each feature can do
// an editor with a text feature can insert text
interface Editor_Text {
Editor_Text.insertText: (value: string) => voidinsertText: (value: stringvalue: string) => void
}
// an editor with a history feature can undo and redo actions
interface Editor_History {
Editor_History.undo: () => voidundo: () => void,
Editor_History.redo: () => voidredo: () => void
}
// connect each feature with a name
type type EditorFeatureMap = {
text: Editor_Text;
history: Editor_History;
}
EditorFeatureMap = {
text: Editor_Texttext: Editor_Text,
history: Editor_Historyhistory: Editor_History
}
Now let's implement those features, as the implementation does not really matter, I will keep that as simple as possible:
interface EditorCore {
EditorCore.text: stringtext: string
}
// features are built using a factory, this lets us share state from the core
// of the editor to the installed features
interface EditorFeature {
EditorFeature.constructEditorApi: (editor: EditorCore) => voidconstructEditorApi: (editor: EditorCoreeditor: EditorCore) => void;
}
const const textFeature: EditorFeaturetextFeature: EditorFeature = {
EditorFeature.constructEditorApi: (editor: EditorCore) => voidconstructEditorApi(editor: EditorCoreeditor) {
const const api: Editor_Textapi: Editor_Text = {
Editor_Text.insertText: (value: string) => voidinsertText(value: stringvalue) {
editor: EditorCoreeditor.EditorCore.text: stringtext += value: stringvalue
}
}
var Object: ObjectConstructorProvides functionality common to all JavaScript objects.
Object.ObjectConstructor.assign<EditorCore, Editor_Text>(target: EditorCore, source: Editor_Text): EditorCore & Editor_Text (+3 overloads)Copy the values of all of the enumerable own properties from one or more source objects to a
target object. Returns the target object.
assign(editor: EditorCoreeditor, const api: Editor_Textapi)
}
}
const const historyFeature: EditorFeaturehistoryFeature: EditorFeature = {
EditorFeature.constructEditorApi: (editor: EditorCore) => voidconstructEditorApi(editor: EditorCoreeditor) {
const const api: Editor_Historyapi: Editor_History = {
Editor_History.undo: () => voidundo() {
// Restore the previous document state.
},
Editor_History.redo: () => voidredo() {
// Reapply the next document state.
}
}
var Object: ObjectConstructorProvides functionality common to all JavaScript objects.
Object.ObjectConstructor.assign<EditorCore, Editor_History>(target: EditorCore, source: Editor_History): EditorCore & Editor_History (+3 overloads)Copy the values of all of the enumerable own properties from one or more source objects to a
target object. Returns the target object.
assign(editor: EditorCoreeditor, const api: Editor_Historyapi)
}
}
type type EditorFeatures = {
text?: EditorFeature | undefined;
history?: EditorFeature | undefined;
}
EditorFeatures = type Partial<T> = { [P in keyof T]?: T[P] | undefined; }Make all properties in T optional
Partial<type Record<K extends keyof any, T> = { [P in K]: T; }Construct a type with a set of properties K of type T
Record<keyof type EditorFeatureMap = {
text: Editor_Text;
history: Editor_History;
}
EditorFeatureMap, EditorFeature>>;
Now, given a list of features, we need to pick only the ones we are interested in from the Map
// 1. the names of the features that were registered
type type RegisteredNames<Features extends EditorFeatures> = keyof Features extends keyof EditorFeatureMap ? keyof EditorFeatureMap & keyof Features : neverRegisteredNames<function (type parameter) Features in type RegisteredNames<Features extends EditorFeatures>Features extends type EditorFeatures = {
text?: EditorFeature | undefined;
history?: EditorFeature | undefined;
}
EditorFeatures> =
type Extract<T, U> = T extends U ? T : neverExtract from T those types that are assignable to U
Extract<keyof function (type parameter) Features in type RegisteredNames<Features extends EditorFeatures>Features, keyof type EditorFeatureMap = {
text: Editor_Text;
history: Editor_History;
}
EditorFeatureMap>
// 2. look those names up in the map, this gives a *union* of feature APIs
type type RegisteredApis<Features extends EditorFeatures> = EditorFeatureMap[Extract<keyof Features, keyof EditorFeatureMap>]RegisteredApis<function (type parameter) Features in type RegisteredApis<Features extends EditorFeatures>Features extends type EditorFeatures = {
text?: EditorFeature | undefined;
history?: EditorFeature | undefined;
}
EditorFeatures> =
type EditorFeatureMap = {
text: Editor_Text;
history: Editor_History;
}
EditorFeatureMap[type RegisteredNames<Features extends EditorFeatures> = keyof Features extends keyof EditorFeatureMap ? keyof EditorFeatureMap & keyof Features : neverRegisteredNames<function (type parameter) Features in type RegisteredApis<Features extends EditorFeatures>Features>]
// 3. the editor must expose *all* of them, so turn the union into an intersection
type type UnionToIntersection<T> = (T extends any ? (a: T) => void : never) extends (a: infer R) => void ? R : neverUnionToIntersection<function (type parameter) T in type UnionToIntersection<T>T> =
(function (type parameter) T in type UnionToIntersection<T>T extends any ? (a: Ta: function (type parameter) T in type UnionToIntersection<T>T) => void : never) extends (a: Ra: infer function (type parameter) RR) => void ? function (type parameter) RR : never
type type SelectFeatures<Features extends EditorFeatures> = (RegisteredApis<Features> extends any ? (a: RegisteredApis<Features>) => void : never) extends (a: infer R) => void ? R : neverSelectFeatures<function (type parameter) Features in type SelectFeatures<Features extends EditorFeatures>Features extends type EditorFeatures = {
text?: EditorFeature | undefined;
history?: EditorFeature | undefined;
}
EditorFeatures> =
type UnionToIntersection<T> = (T extends any ? (a: T) => void : never) extends (a: infer R) => void ? R : neverUnionToIntersection<type RegisteredApis<Features extends EditorFeatures> = EditorFeatureMap[Extract<keyof Features, keyof EditorFeatureMap>]RegisteredApis<function (type parameter) Features in type SelectFeatures<Features extends EditorFeatures>Features>>
Let's now check what each step is doing:
type Names = type RegisteredNames<Features extends EditorFeatures> = keyof Features extends keyof EditorFeatureMap ? keyof EditorFeatureMap & keyof Features : neverRegisteredNames<{ text: EditorFeaturetext: EditorFeature, history: EditorFeaturehistory: EditorFeature }>type Apis = type RegisteredApis<Features extends EditorFeatures> = EditorFeatureMap[Extract<keyof Features, keyof EditorFeatureMap>]RegisteredApis<{ text: EditorFeaturetext: EditorFeature, history: EditorFeaturehistory: EditorFeature }>type Editor = type SelectFeatures<Features extends EditorFeatures> = (RegisteredApis<Features> extends any ? (a: RegisteredApis<Features>) => void : never) extends (a: infer R) => void ? R : neverSelectFeatures<{ text: EditorFeaturetext: EditorFeature, history: EditorFeaturehistory: EditorFeature }>
The first two steps are plain lookup, they convert a feature registration into a feature type. The last one is where it's getting interesting: the map is giving us a union of the features, but we want our editor to expose ALL the features that we enabled. It means that what we really want is an intersection of the features TanStack explains how this type works.
We can now add a constructor and use the types we created:
// we have not implemented this yet
declare function function constructEditor<TFeatures extends EditorFeatures>({ features }: {
features: TFeatures;
}): SelectFeatures<TFeatures>
constructEditor<function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
features: TFeatures;
}): SelectFeatures<TFeatures>
TFeatures extends type EditorFeatures = {
text?: EditorFeature | undefined;
history?: EditorFeature | undefined;
}
EditorFeatures>({features: TFeatures extends EditorFeaturesfeatures}: {features: TFeatures extends EditorFeaturesfeatures: function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
features: TFeatures;
}): SelectFeatures<TFeatures>
TFeatures}): type SelectFeatures<Features extends EditorFeatures> = (RegisteredApis<Features> extends any ? (a: RegisteredApis<Features>) => void : never) extends (a: infer R) => void ? R : neverSelectFeatures<function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
features: TFeatures;
}): SelectFeatures<TFeatures>
TFeatures>
const const editor: Editor_Texteditor = function constructEditor<{
text: EditorFeature;
}>({ features }: {
features: {
text: EditorFeature;
};
}): Editor_Text
constructEditor({
features: {
text: EditorFeature;
}
features: {text: EditorFeaturetext: const textFeature: EditorFeaturetextFeature} // history is not available
});
const editor: Editor_Texteditor.Editor_Text.insertText: (value: string) => voidinsertText("Hello");
const editor: Editor_Texteditor.undo();
const const editorWithHistory: Editor_Text & Editor_HistoryeditorWithHistory = function constructEditor<{
text: EditorFeature;
history: EditorFeature;
}>({ features }: {
features: {
text: EditorFeature;
history: EditorFeature;
};
}): Editor_Text & Editor_History
constructEditor({
features: {
text: EditorFeature;
history: EditorFeature;
}
features: {text: EditorFeaturetext: const textFeature: EditorFeaturetextFeature, history: EditorFeaturehistory: const historyFeature: EditorFeaturehistoryFeature} // history IS available
});
const editorWithHistory: Editor_Text & Editor_HistoryeditorWithHistory.Editor_Text.insertText: (value: string) => voidinsertText("Hello"); // still available
const editorWithHistory: Editor_Text & Editor_HistoryeditorWithHistory.Editor_History.undo: () => voidundo(); // undo and redo are available
As you can see our types now reflects the feature they were built with. This is more or less everything we need for our implementation at the type level. Note that TanStack Table is a bit more complex (mostly to support plugins and to improve DX).
It's now time to implement constructEditor, that will register all added features and return an object with the correct type:
function function constructEditor<TFeatures extends EditorFeatures>({ features }: {
features: TFeatures;
}): EditorCore & SelectFeatures<TFeatures>
constructEditor<function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
features: TFeatures;
}): EditorCore & SelectFeatures<TFeatures>
TFeatures extends type EditorFeatures = {
text?: EditorFeature | undefined;
history?: EditorFeature | undefined;
}
EditorFeatures>({features: TFeatures extends EditorFeaturesfeatures}: {features: TFeatures extends EditorFeaturesfeatures: function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
features: TFeatures;
}): EditorCore & SelectFeatures<TFeatures>
TFeatures}): EditorCore & type SelectFeatures<Features extends EditorFeatures> = (RegisteredApis<Features> extends any ? (a: RegisteredApis<Features>) => void : never) extends (a: infer R) => void ? R : neverSelectFeatures<function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
features: TFeatures;
}): EditorCore & SelectFeatures<TFeatures>
TFeatures> {
const const editor: {
text: string;
}
editor = {
EditorCore.text: stringtext: ""
} satisfies EditorCore;
for(const const feature: EditorFeaturefeature of var Object: ObjectConstructorProvides functionality common to all JavaScript objects.
Object.ObjectConstructor.values<EditorFeature>(o: {
[s: string]: EditorFeature;
} | ArrayLike<EditorFeature>): EditorFeature[] (+1 overload)
Returns an array of values of the enumerable own properties of an object
values(features: TFeatures extends EditorFeaturesfeatures)) {
const feature: EditorFeaturefeature?.EditorFeature.constructEditorApi: (editor: EditorCore) => voidconstructEditorApi(const editor: {
text: string;
}
editor)
}
return const editor: {
text: string;
}
editor as EditorCore & type SelectFeatures<Features extends EditorFeatures> = (RegisteredApis<Features> extends any ? (a: RegisteredApis<Features>) => void : never) extends (a: infer R) => void ? R : neverSelectFeatures<function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
features: TFeatures;
}): EditorCore & SelectFeatures<TFeatures>
TFeatures>;
}
const const editorWithHistory: EditorCore & Editor_Text & Editor_HistoryeditorWithHistory = function constructEditor<{
text: EditorFeature;
history: EditorFeature;
}>({ features }: {
features: {
text: EditorFeature;
history: EditorFeature;
};
}): EditorCore & Editor_Text & Editor_History
constructEditor({
features: {
text: EditorFeature;
history: EditorFeature;
}
features: {text: EditorFeaturetext: const textFeature: EditorFeaturetextFeature, history: EditorFeaturehistory: const historyFeature: EditorFeaturehistoryFeature} // history IS available
});
const editorWithHistory: EditorCore & Editor_Text & Editor_HistoryeditorWithHistory.Editor_Text.insertText: (value: string) => voidinsertText("Hello"); // still available
var console: Consoleconsole.Console.log(...data: any[]): voidThe console.log() static method outputs a message to the console.
log(const editorWithHistory: EditorCore & Editor_Text & Editor_HistoryeditorWithHistory.EditorCore.text: stringtext) // "Hello"
const editorWithHistory: EditorCore & Editor_Text & Editor_HistoryeditorWithHistory.Editor_History.undo: () => voidundo();
This line return editor as EditorCore & SelectFeatures<TFeatures>; is where our types meet our implementation, we have to rely on an unsafe as in the constructor, but this lets our public API be (almost) safe.
where the safety stops
There is one more thing worth noticing: the API is selected from the keys of the feature map, never from the values. The values are of type EditorFeature and carry no information about which feature it is. TanStack Table has the exact same property, rowSelectionFeature is exported as a plain TableFeature, so nothing stops us from registering one feature under another feature's name:
const const features: {
coreReactivityFeature: TableReactivityBindings;
rowSelectionFeature: TableFeature;
}
features = tableFeatures<{
coreReactivityFeature: TableReactivityBindings;
rowSelectionFeature: TableFeature;
}>(features: {
coreReactivityFeature: TableReactivityBindings;
rowSelectionFeature: TableFeature;
} & {}): {
coreReactivityFeature: TableReactivityBindings;
rowSelectionFeature: TableFeature;
}
A helper function to help define the features that are to be imported and applied to a table instance.
Use this utility to make it easier to have the correct type inference for the features that are being imported.
Note: It is recommended to use this utility statically outside of a component.
Alongside feature modules, this object carries everything else that is
statically stitched into the table:
- Row model factories (
sortedRowModel, filteredRowModel, etc.)
- Row model function registries (
sortFns, filterFns, aggregationFns),
whose keys become the valid string values for sortFn, filterFn,
globalFilterFn, and aggregationFn with full inference
- Type-only
tableMeta/columnMeta slots for declaring per-table meta types
instead of using global declaration merging. The values are phantom
(ignored and stripped at runtime); only their types are used.
tableFeatures({
coreReactivityFeature: TableReactivityBindingscoreReactivityFeature: function storeReactivityBindings(): TableReactivityBindingsTanStack Store–based reactivity for vanilla / non-framework use of constructTable,
with createOptionsStore: true so table.optionsStore is available for subscriptions.
storeReactivityBindings(),
// the key says "selection", the value is the sorting feature
rowSelectionFeature: TableFeaturerowSelectionFeature: const rowSortingFeature: TableFeatureFeature that adds row sorting state, defaults, and column/table sorting APIs.
rowSortingFeature,
})
const const table: Table<{
coreReactivityFeature: TableReactivityBindings;
rowSelectionFeature: TableFeature;
}, {
name: string;
}>
table = constructTable<{
coreReactivityFeature: TableReactivityBindings;
rowSelectionFeature: TableFeature;
}, {
name: string;
}>(tableOptions: TableOptions<{
coreReactivityFeature: TableReactivityBindings;
rowSelectionFeature: TableFeature;
}, {
name: string;
}>): Table<{
coreReactivityFeature: TableReactivityBindings;
rowSelectionFeature: TableFeature;
}, {
name: string;
}>
Constructs a table instance from normalized table internals.
This wires core properties, feature prototype APIs, and instance data used by table rendering and row-model operations.
constructTable({ TableOptions_Table<{ coreReactivityFeature: TableReactivityBindings; rowSelectionFeature: TableFeature; }, { name: string; }>.features: {
coreReactivityFeature: TableReactivityBindings;
rowSelectionFeature: TableFeature;
} & {}
The feature modules registered on this table instance.
Feature registration controls which state slices, options, and prototype
APIs are available. This object also carries the table's row model
factories (sortedRowModel, filteredRowModel, etc.), row model function
registries (sortFns, filterFns, aggregationFns), and type-only meta
slots (tableMeta, columnMeta).
features, TableOptions_Table<{ coreReactivityFeature: TableReactivityBindings; rowSelectionFeature: TableFeature; }, { name: string; }>.data: readonly {
name: string;
}[]
The data for the table to display. When the data option changes reference, the table will reprocess the data.
data, TableOptions_Columns<{ coreReactivityFeature: TableReactivityBindings; rowSelectionFeature: TableFeature; }, { name: string; }, unknown>.columns: readonly ColumnDef<{
coreReactivityFeature: TableReactivityBindings;
rowSelectionFeature: TableFeature;
}, {
name: string;
}, unknown>[]
The array of column defs to use for the table.
columns })
// compiles fine, the key is all the type system looks at...
const table: Table<{
coreReactivityFeature: TableReactivityBindings;
rowSelectionFeature: TableFeature;
}, {
name: string;
}>
table.Table_RowSelection<{ coreReactivityFeature: TableReactivityBindings; rowSelectionFeature: TableFeature; }, { name: string; }>.toggleAllRowsSelected: (value?: boolean, opts?: {
deselectAll?: boolean;
}) => void
Selects/deselects all rows in the table.
Deselecting keeps rows that cannot be selected in the selection map unless
opts.deselectAll is true.
toggleAllRowsSelected(true)
// ...and throws at runtime:
// TypeError: table.toggleAllRowsSelected is not a function
At runtime the constructor iterates over Object.values(features), the key is never read and exists purely as a type-level label. TanStack's performance article does not directly discuss this case but Key-based selection is used in both the original and the optimized version of the Table type.
My understanding is that checking the feature values as well, with a branded TableFeature<K> for instance, would add a comparison per feature on every constructTable call.