Skip to content

GridCore data: Remove DataSourceAdapter dynamic member-copy and type _dataSource - #34980

Open
bit-byte0 wants to merge 9 commits into
DevExpress:mainfrom
bit-byte0:refactor/gridcore-datacontroller-type-datasource-adapter-26_2
Open

GridCore data: Remove DataSourceAdapter dynamic member-copy and type _dataSource#34980
bit-byte0 wants to merge 9 commits into
DevExpress:mainfrom
bit-byte0:refactor/gridcore-datacontroller-type-datasource-adapter-26_2

Conversation

@bit-byte0

Copy link
Copy Markdown
Contributor

What

Removes the runtime block in DataSourceAdapter that dynamically copied every DataSource method onto the adapter, and types DataController._dataSource as DataSourceAdapter instead of any

How

Replaced the dynamic copy with explicit typed delegates on the adapter, promoted the adapter's consumed methods to public, and narrowed _dataSource to the concrete adapter subtype in the virtual scrolling and tree_list extenders where subclass methods are used

@bit-byte0 bit-byte0 added the 26_2 label Aug 29, 2026
@bit-byte0 bit-byte0 self-assigned this Aug 29, 2026
…atacontroller-type-datasource-adapter-26_2

# Conflicts:
#	packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts
@bit-byte0
bit-byte0 marked this pull request as ready for review August 31, 2026 06:46
@bit-byte0
bit-byte0 requested a review from a team as a code owner August 31, 2026 06:46
Copilot AI lite review requested due to automatic review settings August 31, 2026 06:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 DataSourceAdapter with explicit delegating methods (and promoted several adapter methods to public).
  • Narrowed DataController._dataSource typing 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 via this._dataSource to preserve this.
  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

  • push is invoked as an unbound function value, which can break if the adapter implementation relies on this. Call it as a method on _dataSource to 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.

Comment thread packages/devextreme/testing/helpers/gridBaseMocks.js Outdated
Copilot AI review requested due to automatic review settings August 31, 2026 07:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 established arguments as any delegation 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. Prefer arguments as any (consistent with other .apply calls in this file).
      dataSource?.setViewportPosition.apply(dataSource, arguments as unknown as []);

Comment thread packages/devextreme/js/__internal/grids/tree_list/m_focus.ts
Copilot AI review requested due to automatic review settings August 31, 2026 07:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.sort as an unbound function, which drops the DataSource this context and can break (DataSource.sort uses instance state). Invoke via property access to preserve this.
  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.group as an unbound function, which drops the DataSource this context and can break (DataSource.group uses instance state). Invoke via property access to preserve this.
  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.select as an unbound function, which drops the DataSource this context and can break (DataSource.select uses instance state). Invoke via property access to preserve this.
  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.paginate as an unbound function, which drops the DataSource this context and will break (DataSource.paginate reads/writes instance fields). Invoke via property access to preserve this.
  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.requireTotalCount as an unbound function, which drops the DataSource this context and will break (DataSource.requireTotalCount reads/writes instance fields). Invoke via property access to preserve this.
  public requireTotalCount(): StoreLoadOptions['requireTotalCount'];
  public requireTotalCount(value: boolean): void;
  public requireTotalCount(value?: boolean): unknown {
    return (this._dataSource.requireTotalCount as (...a: unknown[]) => unknown)(value);
  }

Comment on lines -150 to -158

// 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);
};
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines 335 to 338
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;
}

@Tucchhaa Tucchhaa Aug 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's avoid usage of as:
.fail((...args: unknown[]) => { result.reject(...args); })

Comment on lines 1530 to +1539
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use @ts-expect-error instead of as

private changingHandlerProxy!: (e: ChangingEvent) => void;

protected store!: () => any;
public filter(): StoreLoadOptions['filter'];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to move all getters to a place after init()

return deferred.resolve(-1).promise();
}

if (!dataSource._grouping._updatePagingOptions) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private collapseAll(groupIndex: number): void {
const dataSource = this._dataSource;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const dataSource = this._dataSource as any;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();

@Tucchhaa Tucchhaa Aug 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why ! instead ??

Comment on lines +739 to +740
if ((data as unknown[]).length > 0) {
filter = this._generateOperationFilterByKey(key, (data as unknown[])[0], useGroup);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please avoid usage of as

public reload(reload?: boolean, changesOnly?: boolean): DeferredObj<unknown> {
const rowsScrollController = this._rowsScrollController || this._dataSource;
const itemIndex = rowsScrollController?.getItemIndexByPosition();
const itemIndex = (rowsScrollController as any)?.getItemIndexByPosition();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

propagate args directly:
return dataSource?.viewportSize();
Same applies to viewportHeight, setViewportPosition

…s, reorder getters, drop dead enumerable shim, remove that=this
Copilot AI review requested due to automatic review settings August 31, 2026 13:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 this is lost. Use .apply/.call with this._dataSource to 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 (because this becomes undefined). Delegate via .apply(this._dataSource, args) (see existing pattern in pivot_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);
  }

Comment thread packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts
Copilot AI review requested due to automatic review settings August 31, 2026 13:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • getSortDataSourceParameters can now push a sort descriptor with selector: undefined when _dataController._dataSource is not set. Because isLocalOperations becomes true for an empty remoteOperations object, the dataSource?.getDataIndexGetter() optional call will yield undefined and can break downstream sorting logic. Fall back to sorting by the not-sorted key fields when dataSource is 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 }));
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants