Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export type {
DataSourceTag,
DataSource,
AnyDataSource,
DataObserver,
DataListener,
DataSourceContext,
DataSourceParams,
DataSourceRequest,
Expand Down
18 changes: 18 additions & 0 deletions src/core/types/DataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export interface DataSource<
fetchContext: TFetchContext,
request: TRequest,
) => Promise<TResponse> | TResponse;
observe: (
context: TContext,
params: ActualParams<this, TParams, TRequest>,
options?: TOptions,
) => DataObserver<this>;
tags?: (params: ActualParams<this, TParams, TRequest>) => DataSourceTag[];

transformParams?: (params: TParams) => TRequest;
Expand All @@ -38,6 +43,19 @@ export interface DataSource<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type AnyDataSource = DataSource<any, any, any, any, any, any, any, any, any>;

export interface DataObserver<TDataSource extends AnyDataSource> {
getCurrentState(): DataSourceState<TDataSource>;
updateParams(
params: DataSourceParams<TDataSource>,
options?: DataSourceOptions<TDataSource>,
): void;
subscribe(listener: DataListener<TDataSource>): () => void;
}

export type DataListener<TDataSource extends AnyDataSource> = (
state: DataSourceState<TDataSource>,
) => void;

export type DataSourceContext<TDataSource> =
TDataSource extends DataSource<
infer TContext,
Expand Down
20 changes: 19 additions & 1 deletion src/react-query/hooks/useQueryData.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import type {DataSourceOptions, DataSourceParams, DataSourceState} from '../../core';
import type {
DataSourceContext,
DataSourceOptions,
DataSourceParams,
DataSourceState,
} from '../../core';
import {useData} from '../../react';
import {useInfiniteQueryData} from '../impl/infinite/hooks';
import {usePlainQueryData} from '../impl/plain/hooks';
import type {AnyQueryDataSource} from '../types';
Expand Down Expand Up @@ -27,3 +33,15 @@ export const useQueryData = <TDataSource extends AnyQueryDataSource>(

return state as DataSourceState<TDataSource>;
};

// Do not use it yet. It will be reworked
export const _useQueryData = <TDataSource extends AnyQueryDataSource>(
dataSource: TDataSource,
params: DataSourceParams<TDataSource>,
options?: DataSourceOptions<TDataSource>,
) => {
const context = useQueryContext() as DataSourceContext<TDataSource>;
const [state] = useData<TDataSource>(dataSource, context, params, options);

return state;
};
4 changes: 4 additions & 0 deletions src/react-query/impl/infinite/factory.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import {InfiniteQueryDataObserver} from './observer';
import type {InfiniteQueryDataSource} from './types';

export const makeInfiniteQueryDataSource = <TParams, TRequest, TResponse, TData, TError>(
config: Omit<InfiniteQueryDataSource<TParams, TRequest, TResponse, TData, TError>, 'type'>,
): InfiniteQueryDataSource<TParams, TRequest, TResponse, TData, TError> => ({
...config,
type: 'infinite',
observe(context, params, options) {
return new InfiniteQueryDataObserver(context, this, params, options);
},
});
69 changes: 69 additions & 0 deletions src/react-query/impl/infinite/observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type {InfiniteQueryObserverResult} from '@tanstack/react-query';
import {InfiniteQueryObserver} from '@tanstack/react-query';

import type {
DataListener,
DataObserver,
DataSourceContext,
DataSourceData,
DataSourceError,
DataSourceOptions,
DataSourceParams,
DataSourceResponse,
} from '../../../core';

import type {AnyInfiniteQueryDataSource} from './types';
import {composeOptions, transformResult} from './utils';

export class InfiniteQueryDataObserver<
TDataSource extends AnyInfiniteQueryDataSource,
TContext extends DataSourceContext<TDataSource> = DataSourceContext<TDataSource>,
TParams extends DataSourceParams<TDataSource> = DataSourceParams<TDataSource>,
TResponse extends DataSourceResponse<TDataSource> = DataSourceResponse<TDataSource>,
TData extends DataSourceData<TDataSource> = DataSourceData<TDataSource>,
TError extends DataSourceError<TDataSource> = DataSourceError<TDataSource>,
TOptions extends DataSourceOptions<TDataSource> = DataSourceOptions<TDataSource>,
> implements DataObserver<TDataSource>
{
readonly context: TContext;
readonly dataSource: TDataSource;
readonly observer: InfiniteQueryObserver<TResponse, TError, TData, TResponse>;

constructor(context: TContext, dataSource: TDataSource, params: TParams, options?: TOptions) {
this.context = context;
this.dataSource = dataSource;
this.observer = new InfiniteQueryObserver(
context.queryClient,
this.composeOptions(context, dataSource, params, options),
);
}

getCurrentState() {
return this.transformResult(this.observer.getCurrentResult());
}

updateParams(params: TParams, options?: TOptions) {
this.observer.setOptions(
this.composeOptions(this.context, this.dataSource, params, options),
);
}

subscribe(listener: DataListener<TDataSource>) {
return this.observer.subscribe((result) => {
listener(this.transformResult(result));
});
}

private composeOptions(
context: TContext,
dataSource: TDataSource,
params: TParams,
options?: TOptions,
) {
return composeOptions(context, dataSource, params, options);
}

private transformResult(result: InfiniteQueryObserverResult<TData, TError>) {
return transformResult<TDataSource>(result);
}
}
4 changes: 4 additions & 0 deletions src/react-query/impl/plain/factory.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import {PlainQueryDataObserver} from './observer';
import type {PlainQueryDataSource} from './types';

export const makePlainQueryDataSource = <TParams, TRequest, TResponse, TData, TError>(
config: Omit<PlainQueryDataSource<TParams, TRequest, TResponse, TData, TError>, 'type'>,
): PlainQueryDataSource<TParams, TRequest, TResponse, TData, TError> => ({
...config,
type: 'plain',
observe(context, params, options) {
return new PlainQueryDataObserver(context, this, params, options);
},
});
69 changes: 69 additions & 0 deletions src/react-query/impl/plain/observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type {QueryObserverOptions, QueryObserverResult} from '@tanstack/react-query';
import {QueryObserver} from '@tanstack/react-query';

import type {
DataListener,
DataObserver,
DataSourceContext,
DataSourceData,
DataSourceError,
DataSourceOptions,
DataSourceParams,
DataSourceResponse,
} from '../../../core';

import type {AnyPlainQueryDataSource} from './types';
import {composeOptions, transformResult} from './utils';

export class PlainQueryDataObserver<
TDataSource extends AnyPlainQueryDataSource,
TContext extends DataSourceContext<TDataSource> = DataSourceContext<TDataSource>,
TParams extends DataSourceParams<TDataSource> = DataSourceParams<TDataSource>,
TResponse extends DataSourceResponse<TDataSource> = DataSourceResponse<TDataSource>,
TData extends DataSourceData<TDataSource> = DataSourceData<TDataSource>,
TError extends DataSourceError<TDataSource> = DataSourceError<TDataSource>,
TOptions extends DataSourceOptions<TDataSource> = DataSourceOptions<TDataSource>,
> implements DataObserver<TDataSource>
{
readonly context: TContext;
readonly dataSource: TDataSource;
readonly observer: QueryObserver<TResponse, TError, TData, TResponse>;

constructor(context: TContext, dataSource: TDataSource, params: TParams, options?: TOptions) {
this.context = context;
this.dataSource = dataSource;
this.observer = new QueryObserver(
context.queryClient,
this.composeOptions(context, dataSource, params, options),
);
}

getCurrentState() {
return this.transformResult(this.observer.getCurrentResult());
}

updateParams(params: TParams, options?: TOptions) {
this.observer.setOptions(
this.composeOptions(this.context, this.dataSource, params, options),
);
}

subscribe(listener: DataListener<TDataSource>) {
return this.observer.subscribe((result) => {
listener(this.transformResult(result));
});
}

private composeOptions(
context: TContext,
dataSource: TDataSource,
params: TParams,
options?: TOptions,
): QueryObserverOptions<TResponse, TError, TData, TResponse> {
return composeOptions(context, dataSource, params, options);
}

private transformResult(result: QueryObserverResult<TData, TError>) {
return transformResult<TDataSource>(result);
}
}
2 changes: 2 additions & 0 deletions src/react/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export {useData} from './useData';

export {DataManagerContext, useDataManager} from './DataManagerContext';

export type {WithDataManagerProps} from './withDataManager';
Expand Down
37 changes: 37 additions & 0 deletions src/react/useData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {useEffect, useState} from 'react';

import type {
AnyDataSource,
DataObserver,
DataSourceContext,
DataSourceOptions,
DataSourceParams,
DataSourceState,
} from '../core';
import {composeKey} from '../core';

export const useData = <TDataSource extends AnyDataSource>(
dataSource: TDataSource,
context: DataSourceContext<TDataSource>,
params: DataSourceParams<TDataSource>,
options?: DataSourceOptions<TDataSource>,
): [DataSourceState<TDataSource>, DataObserver<TDataSource>] => {
const [observer] = useState(() => dataSource.observe(context, params, options));
const [state, setState] = useState<DataSourceState<TDataSource>>(() =>
observer.getCurrentState(),
);

useEffect(() => {
return observer.subscribe(setState);
}, [observer]);

const key = composeKey(dataSource, params);

useEffect(() => {
observer.updateParams(params, options);
// Key replaces params and other deps are static
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [options, key]);

return [state, observer];
};