Skip to content

Commit

Permalink
feat: add observer and useData
Browse files Browse the repository at this point in the history
  • Loading branch information
DakEnviy committed Jul 29, 2024
1 parent 820666f commit f26148e
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 1 deletion.
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];
};

0 comments on commit f26148e

Please sign in to comment.