Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/InfiniteQueryDefinition' into In…
Browse files Browse the repository at this point in the history
…finiteQueryDefinition

# Conflicts:
#	packages/toolkit/src/query/core/buildInitiate.ts
#	packages/toolkit/src/query/core/buildSlice.ts
#	packages/toolkit/src/query/core/buildThunks.ts
#	packages/toolkit/src/query/core/module.ts
#	packages/toolkit/src/query/createApi.ts
#	packages/toolkit/src/query/endpointDefinitions.ts
  • Loading branch information
riqts committed Apr 23, 2024
2 parents 1c4d41f + dc6b156 commit e0c952f
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 0 deletions.
176 changes: 176 additions & 0 deletions packages/toolkit/src/query/core/buildInitiate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ export interface StartInfiniteQueryActionCreatorOptions {
previous?: boolean
}

export interface StartInfiniteQueryActionCreatorOptions {
subscribe?: boolean
forceRefetch?: boolean | number
subscriptionOptions?: SubscriptionOptions
infiniteQueryOptions?: InfiniteQueryConfigOptions
direction?: "forward" | "backwards"
[forceQueryFnSymbol]?: () => QueryReturnValue
data?: InfiniteData<unknown>
param?: unknown
previous?: boolean
}

type StartQueryActionCreator<
D extends QueryDefinition<any, any, any, any, any>,
> = (
Expand All @@ -91,6 +103,15 @@ type StartInfiniteQueryActionCreator<
options?: StartInfiniteQueryActionCreatorOptions
) => (dispatch: ThunkDispatch<any, any, UnknownAction>, getState: () => any) => InfiniteQueryActionCreatorResult<any>

// placeholder type which
// may attempt to derive the list of args to query in pagination
type StartInfiniteQueryActionCreator<
D extends QueryDefinition<any, any, any, any, any>
> = (
arg: QueryArgFrom<D>,
options?: StartInfiniteQueryActionCreatorOptions
) => (dispatch: ThunkDispatch<any, any, UnknownAction>, getState: () => any) => InfiniteQueryActionCreatorResult<any>

export type QueryActionCreatorResult<
D extends QueryDefinition<any, any, any, any>,
> = SafePromise<QueryResultSelectorResult<D>> & {
Expand Down Expand Up @@ -123,6 +144,22 @@ export type InfiniteQueryActionCreatorResult<
queryCacheKey: string
}

export type InfiniteQueryActionCreatorResult<
D extends QueryDefinition<any, any, any, any>
> = Promise<QueryResultSelectorResult<D>> & {
arg: QueryArgFrom<D>
requestId: string
subscriptionOptions: SubscriptionOptions | undefined
abort(): void
unwrap(): Promise<ResultTypeFrom<D>>
unsubscribe(): void
refetch(): QueryActionCreatorResult<D>
fetchNextPage(): QueryActionCreatorResult<D>
fetchPreviousPage(): QueryActionCreatorResult<D>
updateSubscriptionOptions(options: SubscriptionOptions): void
queryCacheKey: string
}

type StartMutationActionCreator<
D extends MutationDefinition<any, any, any, any>,
> = (
Expand Down Expand Up @@ -603,6 +640,145 @@ You must add the middleware for RTK-Query to function correctly!`,
return infiniteQueryAction
}

// Concept for the pagination thunk which queries for each page

function buildInitiateInfiniteQuery(
endpointName: string,
endpointDefinition: QueryDefinition<any, any, any, any>,
pages?: number,
) {
const infiniteQueryAction: StartInfiniteQueryActionCreator<any> =
(
arg,
{
subscribe = true,
forceRefetch,
subscriptionOptions,
[forceQueryFnSymbol]: forceQueryFn,
direction,
data = { pages: [], pageParams: [] },
param = arg,
previous
} = {}
) =>
(dispatch, getState) => {
const queryCacheKey = serializeQueryArgs({
queryArgs: param,
endpointDefinition,
endpointName,
})


const thunk = infiniteQueryThunk({
type: 'query',
subscribe,
forceRefetch: forceRefetch,
subscriptionOptions,
endpointName,
originalArgs: arg,
queryCacheKey,
[forceQueryFnSymbol]: forceQueryFn,
data,
param,
previous,
direction
})
const selector = (
api.endpoints[endpointName] as ApiEndpointQuery<any, any>
).select(arg)

const thunkResult = dispatch(thunk)
const stateAfter = selector(getState())

middlewareWarning(dispatch)

const { requestId, abort } = thunkResult

const skippedSynchronously = stateAfter.requestId !== requestId

const runningQuery = runningQueries.get(dispatch)?.[queryCacheKey]
const selectFromState = () => selector(getState())

const statePromise: InfiniteQueryActionCreatorResult<any> = Object.assign(
forceQueryFn
? // a query has been forced (upsertQueryData)
// -> we want to resolve it once data has been written with the data that will be written
thunkResult.then(selectFromState)
: skippedSynchronously && !runningQuery
? // a query has been skipped due to a condition and we do not have any currently running query
// -> we want to resolve it immediately with the current data
Promise.resolve(stateAfter)
: // query just started or one is already in flight
// -> wait for the running query, then resolve with data from after that
Promise.all([runningQuery, thunkResult]).then(selectFromState),
{
arg,
requestId,
subscriptionOptions,
queryCacheKey,
abort,
async unwrap() {
const result = await statePromise

if (result.isError) {
throw result.error
}

return result.data
},
refetch: () =>
dispatch(
infiniteQueryAction(arg, { subscribe: false, forceRefetch: true })
),
fetchNextPage: () =>
dispatch(
infiniteQueryAction(arg, { subscribe: false, forceRefetch: true, direction: "forward"})
),
fetchPreviousPage: () =>
dispatch(
infiniteQueryAction(arg, {subscribe: false, forceRefetch: true, direction: "backwards"})
),
unsubscribe() {
if (subscribe)
dispatch(
unsubscribeQueryResult({
queryCacheKey,
requestId,
})
)
},
updateSubscriptionOptions(options: SubscriptionOptions) {
statePromise.subscriptionOptions = options
dispatch(
updateSubscriptionOptions({
endpointName,
requestId,
queryCacheKey,
options,
})
)
},
}
)

if (!runningQuery && !skippedSynchronously && !forceQueryFn) {
const running = runningQueries.get(dispatch) || {}
running[queryCacheKey] = statePromise
runningQueries.set(dispatch, running)

statePromise.then(() => {
delete running[queryCacheKey]
if (!countObjectKeys(running)) {
runningQueries.delete(dispatch)
}
})
}
return statePromise

}
return infiniteQueryAction
}

function buildInitiateMutation(
endpointName: string,
): StartMutationActionCreator<any> {
Expand Down
14 changes: 14 additions & 0 deletions packages/toolkit/src/query/core/buildMiddleware/cacheLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,20 @@ declare module '../../endpointDefinitions' {
): Promise<void> | void
}

// copying QueryDefinition to get past initial build
interface InfiniteQueryExtraOptions<
TagTypes extends string,
ResultType,
QueryArg,
BaseQuery extends BaseQueryFn,
ReducerPath extends string = string
> {
onCacheEntryAdded?(
arg: QueryArg,
api: QueryCacheLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>
): Promise<void> | void
}

interface MutationExtraOptions<
TagTypes extends string,
ResultType,
Expand Down
14 changes: 14 additions & 0 deletions packages/toolkit/src/query/core/buildMiddleware/queryLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,20 @@ declare module '../../endpointDefinitions' {
): Promise<void> | void
}

// temporarily cloned QueryOptions again to just get the definition to build for now
interface InfiniteQueryExtraOptions<
TagTypes extends string,
ResultType,
QueryArg,
BaseQuery extends BaseQueryFn,
ReducerPath extends string = string
> {
onQueryStarted?(
arg: QueryArg,
api: QueryLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>
): Promise<void> | void
}

interface MutationExtraOptions<
TagTypes extends string,
ResultType,
Expand Down
33 changes: 33 additions & 0 deletions packages/toolkit/src/query/core/buildSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,39 @@ export function buildSlice({
})


const infiniteQuerySlice = createSlice({
name: `${reducerPath}/infinitequeries`,
initialState: initialState as QueryState<any>,
reducers: {
changeDirection: {
reducer(
draft,
{ payload: { queryCacheKey } }: PayloadAction<QuerySubstateIdentifier>
) {
},
prepare: prepareAutoBatched<QuerySubstateIdentifier>(),
},
combineArgsFromSelection: {
reducer(
draft,
{
payload: { queryCacheKey, patches },
}: PayloadAction<
QuerySubstateIdentifier & { patches: readonly Patch[] }
>
) {
updateQuerySubstateIfExists(draft, queryCacheKey, (substate) => {
substate.originalArgs = substate
})
},
prepare: prepareAutoBatched<
QuerySubstateIdentifier & { patches: readonly Patch[] }
>(),
},
},
})


// Dummy slice to generate actions
const subscriptionSlice = createSlice({
name: `${reducerPath}/subscriptions`,
Expand Down
81 changes: 81 additions & 0 deletions packages/toolkit/src/query/core/buildThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ export interface InfiniteQueryThunkArg
direction?: 'forward' | "backwards"
}

export interface InfiniteQueryThunkArg
extends QuerySubstateIdentifier,
StartInfiniteQueryActionCreatorOptions {
type: `query`
originalArgs: unknown
endpointName: string
data: InfiniteData<unknown>
param: unknown
previous?: boolean
direction?: 'forward' | "backwards"
}

export interface MutationThunkArg {
type: 'mutation'
originalArgs: unknown
Expand Down Expand Up @@ -308,6 +320,16 @@ export function buildThunks<
return max && newItems.length > max ? newItems.slice(1) : newItems
}

function addToStart<T>(items: Array<T>, item: T, max = 0): Array<T> {
const newItems = [item, ...items]
return max && newItems.length > max ? newItems.slice(0, -1) : newItems
}

function addToEnd<T>(items: Array<T>, item: T, max = 0): Array<T> {
const newItems = [...items, item]
return max && newItems.length > max ? newItems.slice(1) : newItems
}

const updateQueryData: UpdateQueryDataThunk<EndpointDefinitions, State> =
(endpointName, args, updateRecipe, updateProvided = true) =>
(dispatch, getState) => {
Expand Down Expand Up @@ -774,6 +796,65 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".`
dispatchConditionRejection: true,
})

const infiniteQueryThunk = createAsyncThunk<
ThunkResult,
InfiniteQueryThunkArg,
ThunkApiMetaConfig & { state: RootState<any, string, ReducerPath> }
>(`${reducerPath}/executeQuery`, executeEndpoint, {
getPendingMeta() {
return { startedTimeStamp: Date.now(), [SHOULD_AUTOBATCH]: true }
},
condition(queryThunkArgs, { getState }) {
const state = getState()

const requestState =
state[reducerPath]?.queries?.[queryThunkArgs.queryCacheKey]
const fulfilledVal = requestState?.fulfilledTimeStamp
const currentArg = queryThunkArgs.originalArgs
const previousArg = requestState?.originalArgs
const endpointDefinition =
endpointDefinitions[queryThunkArgs.endpointName]

// Order of these checks matters.
// In order for `upsertQueryData` to successfully run while an existing request is in flight,
/// we have to check for that first, otherwise `queryThunk` will bail out and not run at all.
// if (isUpsertQuery(queryThunkArgs)) {
// return true
// }

// Don't retry a request that's currently in-flight
if (requestState?.status === 'pending') {
return false
}

// if this is forced, continue
// if (isForcedQuery(queryThunkArgs, state)) {
// return true
// }

if (
isQueryDefinition(endpointDefinition) &&
endpointDefinition?.forceRefetch?.({
currentArg,
previousArg,
endpointState: requestState,
state,
})
) {
return true
}

// Pull from the cache unless we explicitly force refetch or qualify based on time
if (fulfilledVal) {
// Value is cached and we didn't specify to refresh, skip it.
return false
}

return true
},
dispatchConditionRejection: true,
})

const mutationThunk = createAsyncThunk<
ThunkResult,
MutationThunkArg,
Expand Down

0 comments on commit e0c952f

Please sign in to comment.