GridCore data: Remove DataSourceAdapter dynamic member-copy and type _dataSource - #34980
Conversation
…atacontroller-type-datasource-adapter-26_2 # Conflicts: # packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts
…urce for unconditional delegates
There was a problem hiding this comment.
Pull request overview
This PR removes DataSourceAdapter’s runtime “copy every DataSource method onto the adapter” behavior and replaces it with explicitly declared, typed delegating methods, while narrowing DataController._dataSource from any to DataSourceAdapter | null and adding more specific _dataSource typings in TreeList/virtual scrolling extenders.
Changes:
- Replaced dynamic member-copying in
DataSourceAdapterwith explicit delegating methods (and promoted several adapter methods topublic). - Narrowed
DataController._dataSourcetyping and updated downstream grid/tree-list modules to accommodate the stricter type surface. - Updated test mocks to provide newly required DataSource methods.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/devextreme/testing/helpers/gridBaseMocks.js | Extends mock DataSource shape to satisfy new adapter delegates. |
| packages/devextreme/js/__internal/grids/tree_list/m_virtual_scrolling.ts | Adjusts TreeList virtual scrolling controller/adapter visibility and access patterns. |
| packages/devextreme/js/__internal/grids/tree_list/m_focus.ts | Adds TreeList-specific adapter typing and additional casts for stricter _dataSource typing. |
| packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts | Promotes various TreeList adapter methods to public for external use. |
| packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts | Narrows TreeList _dataSource typing and adjusts calls for stricter types. |
| packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts | Promotes virtual scrolling adapter methods to public for typed access. |
| packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts | Adds a concrete virtual-scrolling adapter instance type for _dataSource. |
| packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts | Updates focus logic to use stricter _dataSource typing/non-null assertions. |
| packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts | Removes dynamic method copying; introduces explicit delegates and makes several members public. |
| packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts | Types _dataSource as `DataSourceAdapter |
| packages/devextreme/js/__internal/grids/data_grid/summary/extenders/summary_data_controller.ts | Casts _dataSource for access to DataGrid-only aggregates API under stricter typing. |
| packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts | Promotes grouping adapter methods to public to match new adapter surface. |
| packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/grouping_data_controller.ts | Casts _dataSource to access grouping-only adapter APIs. |
| packages/devextreme/js/__internal/grids/data_grid/focus/m_focus.ts | Casts _dataSource for grouped focus calculations under stricter typing. |
| packages/devextreme/js/__internal/grids/data_grid/export/m_export.ts | Adds null-safe access for _dataController._dataSource remoteOperations usage. |
Suppressed comments (2)
packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts:130
- Same receiver-binding issue as above: paginate/requireTotalCount are invoked as unbound function values, which can break if the DataSource method uses
this. Call them viathis._dataSourceto preservethis.
public paginate(): boolean | undefined;
public paginate(value: boolean): void;
public paginate(value?: boolean): boolean | undefined {
return (this._dataSource.paginate as (...a: unknown[]) => boolean | undefined)(value);
}
packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts:1788
pushis invoked as an unbound function value, which can break if the adapter implementation relies onthis. Call it as a method on_dataSourceto preserve the receiver.
public push(...args: unknown[]): unknown {
return (this._dataSource?.push as ((...a: unknown[]) => unknown) | undefined)?.(...args);
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…mock cancel returns boolean
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
Suppressed comments (2)
packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts:562
- Same issue as above:
arguments as unknown as []is an empty-tuple cast that hides the forwarded argument list. Prefer the establishedarguments as anydelegation pattern used throughout this file.
return dataSource?.viewportItemSize.apply(dataSource, arguments as unknown as []);
packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts:574
- Same delegation typing issue:
arguments as unknown as []suggests an empty argument list. Preferarguments as any(consistent with other.applycalls in this file).
dataSource?.setViewportPosition.apply(dataSource, arguments as unknown as []);
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Suppressed comments (5)
packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts:112
- This delegate calls
DataSource.sortas an unbound function, which drops the DataSourcethiscontext and can break (DataSource.sort uses instance state). Invoke via property access to preservethis.
public sort(): StoreLoadOptions['sort'];
public sort(sortExpr: StoreLoadOptions['sort']): void;
public sort(...args: unknown[]): unknown {
return (this._dataSource.sort as (...a: unknown[]) => unknown)(...args);
}
packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts:118
- This delegate calls
DataSource.groupas an unbound function, which drops the DataSourcethiscontext and can break (DataSource.group uses instance state). Invoke via property access to preservethis.
public group(): StoreLoadOptions['group'];
public group(groupExpr: StoreLoadOptions['group']): void;
public group(...args: unknown[]): unknown {
return (this._dataSource.group as (...a: unknown[]) => unknown)(...args);
}
packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts:124
- This delegate calls
DataSource.selectas an unbound function, which drops the DataSourcethiscontext and can break (DataSource.select uses instance state). Invoke via property access to preservethis.
public select(): StoreLoadOptions['select'];
public select(selectExpr: StoreLoadOptions['select']): void;
public select(...args: unknown[]): unknown {
return (this._dataSource.select as (...a: unknown[]) => unknown)(...args);
}
packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts:130
- This delegate calls
DataSource.paginateas an unbound function, which drops the DataSourcethiscontext and will break (DataSource.paginate reads/writes instance fields). Invoke via property access to preservethis.
public paginate(): boolean | undefined;
public paginate(value: boolean): void;
public paginate(value?: boolean): boolean | undefined {
return (this._dataSource.paginate as (...a: unknown[]) => boolean | undefined)(value);
}
packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts:136
- This delegate calls
DataSource.requireTotalCountas an unbound function, which drops the DataSourcethiscontext and will break (DataSource.requireTotalCount reads/writes instance fields). Invoke via property access to preservethis.
public requireTotalCount(): StoreLoadOptions['requireTotalCount'];
public requireTotalCount(value: boolean): void;
public requireTotalCount(value?: boolean): unknown {
return (this._dataSource.requireTotalCount as (...a: unknown[]) => unknown)(value);
}
|
|
||
| // TODO: remove copying dataSource's members | ||
| each(dataSource, (memberName, member) => { | ||
| if (!that[memberName] && isFunction(member)) { | ||
| that[memberName] = function () { | ||
| return this._dataSource[memberName].apply(this._dataSource, arguments); | ||
| }; | ||
| } | ||
| }); |
There was a problem hiding this comment.
I suggest to remove this code from m_data_source.ts since it's not needed anymore:
https://github.com/DevExpress/DevExtreme/blob/main/packages/devextreme/js/__internal/data/data_source/data_source.ts#L792-L808
| public getDataSource(): DataSource | null | undefined { | ||
| const adapter: DataSourceAdapterLike | null | undefined = this._dataSource; | ||
| const adapter = this._dataSource as unknown as DataSourceAdapterLike | null | undefined; | ||
| return adapter ? adapter._dataSource : null; | ||
| } |
There was a problem hiding this comment.
public getDataSource(): DataSource | undefined {
return this._dataSource?._dataSource;
}then make DataSourceAdapter._dataSource to be public and delete DataSourceAdapterLike type as it is not used anywhere else
| this._applyFilter(); | ||
| } else { | ||
| this._currentOperationTypes = dataSource.operationTypes(); | ||
| this._currentOperationTypes = dataSource.operationTypes() ?? null; |
There was a problem hiding this comment.
Maybe move this normalization to DataSourceAdapter.operationTypes()?:
public operationTypes(): OperationTypes | null {
return this._operationTypes ?? null;
}
| this._isPaging = false; | ||
| result.resolve(...args); | ||
| }).fail(result.reject); | ||
| }).fail(result.reject as (...args: unknown[]) => void); |
There was a problem hiding this comment.
let's avoid usage of as:
.fail((...args: unknown[]) => { result.reject(...args); })
| dataSource.load(loadOptions) | ||
| .done((loadedItems: RawItemData[], extra: LoadOperation['extra']): void => { | ||
| .done((loadedItems: unknown, extra: unknown): void => { | ||
| const items = this._processItems( | ||
| this._beforeProcessItems(loadedItems), | ||
| this._beforeProcessItems(loadedItems as RawItemData[]), | ||
| { changeType: 'loadingAll' }, | ||
| ); | ||
| // @ts-expect-error DataGrid-only summary leaks into grid_core | ||
| d.resolve(items, extra?.summary); | ||
| d.resolve(items, (extra as LoadOperation['extra'])?.summary); | ||
| }) | ||
| .fail(d.reject); | ||
| .fail(d.reject as (...args: unknown[]) => void); |
There was a problem hiding this comment.
Can keep these as for now, since this method will be refactored in the PR with CustomLoadPipeline:
https://github.com/DevExpress/DevExtreme/pull/34985/changes#diff-2fcaa9c5b931f7dac59f24898189fb29e1e4376b3f81f7ae612f670f6f195d04R1498-R1511
| } | ||
|
|
||
| const pageIndex = dataSource.pageIndex(); | ||
| const pageIndex = dataSource.pageIndex() as unknown as number; |
There was a problem hiding this comment.
I suggest to rewrite this to:
// @ts-expect-error badly typed DataSourceAdapter
const pageIndex: number = dataSource.pageIndex();Because if we add types to DataSourceAdapter.pageIndex(), the ts will not show the error about redundant cast
|
|
||
| const loadResult: DeferredObj<unknown> = dataSource[optionName === 'pageIndex' ? 'load' : 'reload'](); | ||
| const loadResult: DeferredObj<unknown> = ( | ||
| dataSource[optionName === 'pageIndex' ? 'load' : 'reload'] as () => DeferredObj<unknown> |
There was a problem hiding this comment.
let's use @ts-expect-error instead of as
|
|
||
| public push(...args: unknown[]): unknown { | ||
| return this._dataSource?.push(...args); | ||
| return (this._dataSource?.push as ((...a: unknown[]) => unknown) | undefined)?.(...args); |
There was a problem hiding this comment.
let's use @ts-expect-error instead of as
| private changingHandlerProxy!: (e: ChangingEvent) => void; | ||
|
|
||
| protected store!: () => any; | ||
| public filter(): StoreLoadOptions['filter']; |
There was a problem hiding this comment.
I suggest to move all getters to a place after init()
| return deferred.resolve(-1).promise(); | ||
| } | ||
|
|
||
| if (!dataSource._grouping._updatePagingOptions) { |
There was a problem hiding this comment.
add @ts-expect-error here and remove as any from here:
https://github.com/DevExpress/DevExtreme/pull/34980/changes#diff-f03368d4500b8ecb4fbf076a07c3307cc0dca776f94f936f3e45e0d62be02c57R91
| private collapseAll(groupIndex: number): void { | ||
| const dataSource = this._dataSource; | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const dataSource = this._dataSource as any; |
There was a problem hiding this comment.
use @ts-expect-error where needed instead of casting to any
| private expandAll(groupIndex: number): void { | ||
| const dataSource = this._dataSource; | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const dataSource = this._dataSource as any; |
There was a problem hiding this comment.
use @ts-expect-error where needed instead of casting to any
| protected changeRowExpandCore(key: RowKey): DeferredObj<unknown> { | ||
| const dataSource = this._dataSource; | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const dataSource = this._dataSource as any; |
There was a problem hiding this comment.
use @ts-expect-error where needed instead of casting to any
| private isRowExpanded(key: RowKey): boolean { | ||
| return !!this._dataSource?.isRowExpanded(key); | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| return !!(this._dataSource as any)?.isRowExpanded(key); |
There was a problem hiding this comment.
use @ts-expect-error where needed instead of casting to any
| const summaryItemIndex = getSummaryItemIndex(this.option('summary.totalItems'), summaryItemName); | ||
| const aggregates = this._dataSource.totalAggregates(); | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const aggregates = (this._dataSource as any).totalAggregates(); |
There was a problem hiding this comment.
use @ts-expect-error instead of casting to any
| if (dataSource && summaryTotalItems?.length) { | ||
| const totalAggregates = dataSource.totalAggregates(); | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const totalAggregates = (dataSource as any).totalAggregates(); |
There was a problem hiding this comment.
use @ts-expect-error instead of casting to any
| result = result || []; | ||
| if (isLocalOperations) { | ||
| result.push({ selector: dataSource.getDataIndexGetter(), desc: false }); | ||
| result.push({ selector: dataSource!.getDataIndexGetter(), desc: false }); |
| if ((data as unknown[]).length > 0) { | ||
| filter = this._generateOperationFilterByKey(key, (data as unknown[])[0], useGroup); |
| public reload(reload?: boolean, changesOnly?: boolean): DeferredObj<unknown> { | ||
| const rowsScrollController = this._rowsScrollController || this._dataSource; | ||
| const itemIndex = rowsScrollController?.getItemIndexByPosition(); | ||
| const itemIndex = (rowsScrollController as any)?.getItemIndexByPosition(); |
There was a problem hiding this comment.
please avoid usage of as any in this file, use @ts-expect-error instead
| } | ||
|
|
||
| return dataSource?.viewportSize.apply(dataSource, arguments); | ||
| return dataSource?.viewportSize.apply(dataSource, arguments as any); |
There was a problem hiding this comment.
propagate args directly:
return dataSource?.viewportSize();
Same applies to viewportHeight, setViewportPosition
…s, reorder getters, drop dead enumerable shim, remove that=this
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts:200
- The proxy methods (select/paginate/requireTotalCount) also call unbound DataSource functions; this can break because
thisis lost. Use.apply/.callwiththis._dataSourceto preserve DataSource instance context.
public select(): StoreLoadOptions['select'];
public select(selectExpr: StoreLoadOptions['select']): void;
public select(...args: unknown[]): unknown {
return (this._dataSource.select as (...a: unknown[]) => unknown)(...args);
}
packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts:170
- The proxy methods (filter/sort/group) call the DataSource functions without binding
this, so any DataSource implementation that relies on instance state will break (becausethisbecomes undefined). Delegate via.apply(this._dataSource, args)(see existing pattern inpivot_grid/remote_store/m_remote_store.ts).
public filter(): StoreLoadOptions['filter'];
public filter(filterExpr: StoreLoadOptions['filter']): void;
public filter(...args: unknown[]): unknown {
return (this._dataSource.filter as (...a: unknown[]) => unknown)(...args);
}
…ap relies on it too
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Suppressed comments (1)
packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts:552
getSortDataSourceParameterscan now push a sort descriptor withselector: undefinedwhen_dataController._dataSourceis not set. BecauseisLocalOperationsbecomestruefor an emptyremoteOperationsobject, thedataSource?.getDataIndexGetter()optional call will yieldundefinedand can break downstream sorting logic. Fall back to sorting by the not-sorted key fields whendataSourceis missing.
if (notSortedKeys.length) {
result = result || [];
if (isLocalOperations) {
result.push({ selector: dataSource?.getDataIndexGetter(), desc: false });
} else {
notSortedKeys.forEach((notSortedKey) => result.push({ selector: notSortedKey, desc: false }));
}
What
Removes the runtime block in DataSourceAdapter that dynamically copied every
DataSourcemethod onto the adapter, and typesDataController._dataSourceasDataSourceAdapterinstead ofanyHow
Replaced the dynamic copy with explicit typed delegates on the adapter, promoted the adapter's consumed methods to
public, and narrowed_dataSourceto the concrete adapter subtype in the virtual scrolling and tree_list extenders where subclass methods are used