Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(loaders): make data always possibly undefined #506

Merged
merged 1 commit into from
Sep 9, 2024
Merged
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
53 changes: 16 additions & 37 deletions src/data-loaders/createDataLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@ import { type _Awaitable } from '../utils'
/**
* Base type for a data loader entry. Each Data Loader has its own entry in the `loaderEntries` (accessible via `[LOADER_ENTRIES_KEY]`) map.
*/
export interface DataLoaderEntryBase<
isLazy extends boolean = boolean,
Data = unknown,
> {
export interface DataLoaderEntryBase<Data = unknown> {
/**
* Data stored in the entry.
*/
data: ShallowRef<_DataMaybeLazy<Data, isLazy>>
data: ShallowRef<Data | undefined>

/**
* Error if there was an error.
Expand All @@ -32,7 +29,7 @@ export interface DataLoaderEntryBase<
*/
isLoading: ShallowRef<boolean>

options: DefineDataLoaderOptionsBase<isLazy>
options: DefineDataLoaderOptionsBase

/**
* Called by the navigation guard when the navigation is duplicated. Should be used to reset pendingTo and pendingLoad and any other property that should be reset.
Expand Down Expand Up @@ -86,15 +83,15 @@ export interface CreateDataLoaderOptions<
after: <Data = unknown>(data: Data, context: Context) => unknown
}

export interface DefineDataLoaderOptionsBase<isLazy extends boolean> {
export interface DefineDataLoaderOptionsBase {
/**
* Whether the data should be lazy loaded without blocking the client side navigation or not. When set to true, the loader will no longer block the navigation and the returned composable can be called even
* without having the data ready.
*
* @defaultValue `false`
*/
lazy?:
| isLazy
| boolean
| ((
to: RouteLocationNormalizedLoaded,
from?: RouteLocationNormalizedLoaded
Expand Down Expand Up @@ -143,7 +140,6 @@ export const toLazyValue = (
* - `after-load`: the data is committed after all non-lazy loaders have finished loading.
*/
export type DefineDataLoaderCommit = 'immediate' | 'after-load'
// TODO: is after-load fine or is it better to have an after-navigation instead

export interface DataLoaderContextBase {
/**
Expand All @@ -153,11 +149,11 @@ export interface DataLoaderContextBase {
}

export interface DefineDataLoader<Context extends DataLoaderContextBase> {
<isLazy extends boolean, Data>(
<Data>(
fn: DefineLoaderFn<Data, Context>,
options?: DefineDataLoaderOptionsBase<isLazy>
options?: DefineDataLoaderOptionsBase
// TODO: or a generic that allows a more complex UseDataLoader
): UseDataLoader<isLazy, Data>
): UseDataLoader<Data>
}

// TODO: should be in each data loader. Refactor the base type to accept the needed generics
Expand All @@ -166,10 +162,7 @@ export interface DefineDataLoader<Context extends DataLoaderContextBase> {
* Data Loader composable returned by `defineLoader()`.
* @see {@link DefineDataLoader}
*/
export interface UseDataLoader<
isLazy extends boolean = boolean,
Data = unknown,
> {
export interface UseDataLoader<Data = unknown> {
[IS_USE_DATA_LOADER_KEY]: true

/**
Expand Down Expand Up @@ -198,24 +191,21 @@ export interface UseDataLoader<
// `return new NavigationResult()` in the loader
Exclude<Data, NavigationResult>,
// or use it as a composable
UseDataLoaderResult<isLazy, Exclude<Data, NavigationResult>>
UseDataLoaderResult<Exclude<Data, NavigationResult>>
>

/**
* Internals of the data loader.
* @internal
*/
_: UseDataLoaderInternals<isLazy, Exclude<Data, NavigationResult>>
_: UseDataLoaderInternals<Exclude<Data, NavigationResult>>
}

/**
* Internal properties of a data loader composable. Used by the internal implementation of `defineLoader()`. **Should
* not be used in application code.**
*/
export interface UseDataLoaderInternals<
isLazy extends boolean = boolean,
Data = unknown,
> {
export interface UseDataLoaderInternals<Data = unknown> {
/**
* Loads the data from the cache if possible, otherwise loads it from the loader and awaits it.
*
Expand All @@ -234,36 +224,25 @@ export interface UseDataLoaderInternals<
/**
* Resolved options for the loader.
*/
options: DefineDataLoaderOptionsBase<isLazy>
options: DefineDataLoaderOptionsBase

/**
* Gets the entry associated with the router instance. Assumes the data loader has been loaded and that the entry
* exists.
*
* @param router - router instance
*/
getEntry(router: Router): DataLoaderEntryBase<isLazy, Data>
getEntry(router: Router): DataLoaderEntryBase<Data>
}

/**
* Generates the type for a `Ref` of a data loader based on the value of `lazy`.
* @internal
*/
export type _DataMaybeLazy<Data, isLazy extends boolean = boolean> =
// no lazy provided, default value is false
boolean extends isLazy ? Data : true extends isLazy ? Data | undefined : Data

/**
* Return value of a loader composable defined with `defineLoader()`.
*/
export interface UseDataLoaderResult<
isLazy extends boolean = boolean,
Data = unknown,
> {
export interface UseDataLoaderResult<Data = unknown> {
/**
* Data returned by the loader. If the data loader is lazy, it will be undefined until the first load.
*/
data: ShallowRef<_DataMaybeLazy<Data, isLazy>>
data: ShallowRef<Data | undefined>

/**
* Whether there is an ongoing request.
Expand Down
76 changes: 30 additions & 46 deletions src/data-loaders/defineColadaLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import type {
DefineLoaderFn,
UseDataLoader,
UseDataLoaderResult,
_DataMaybeLazy,
_DefineLoaderEntryMap,
} from 'unplugin-vue-router/runtime'
import {
Expand Down Expand Up @@ -60,40 +59,32 @@ import { toLazyValue } from './createDataLoader'
* @param loader - function that returns a promise with the data
* @param options - options to configure the data loader
*/
export function defineColadaLoader<
Name extends keyof RouteMap,
Data,
isLazy extends boolean,
>(
export function defineColadaLoader<Name extends keyof RouteMap, Data>(
name: Name,
options: DefineDataColadaLoaderOptions<isLazy, Name, Data>
): UseDataLoaderColada<isLazy, Data>
export function defineColadaLoader<Data, isLazy extends boolean>(
options: DefineDataColadaLoaderOptions<isLazy, keyof RouteMap, Data>
): UseDataLoaderColada<isLazy, Data>
options: DefineDataColadaLoaderOptions<Name, Data>
): UseDataLoaderColada<Data>
export function defineColadaLoader<Data>(
options: DefineDataColadaLoaderOptions<keyof RouteMap, Data>
): UseDataLoaderColada<Data>

export function defineColadaLoader<Data, isLazy extends boolean>(
export function defineColadaLoader<Data>(
nameOrOptions:
| keyof RouteMap
| DefineDataColadaLoaderOptions<isLazy, keyof RouteMap, Data>,
_options?: DefineDataColadaLoaderOptions<isLazy, keyof RouteMap, Data>
): UseDataLoaderColada<isLazy, Data> {
| DefineDataColadaLoaderOptions<keyof RouteMap, Data>,
_options?: DefineDataColadaLoaderOptions<keyof RouteMap, Data>
): UseDataLoaderColada<Data> {
// TODO: make it DEV only and remove the first argument in production mode
// resolve option overrides
_options =
_options ||
(nameOrOptions as DefineDataColadaLoaderOptions<
isLazy,
keyof RouteMap,
Data
>)
(nameOrOptions as DefineDataColadaLoaderOptions<keyof RouteMap, Data>)
const loader = _options.query

const options = {
...DEFAULT_DEFINE_LOADER_OPTIONS,
..._options,
commit: _options?.commit || 'after-load',
} as DefineDataColadaLoaderOptions<isLazy, keyof RouteMap, Data>
} as DefineDataColadaLoaderOptions<keyof RouteMap, Data>

let isInitial = true

Expand All @@ -104,18 +95,16 @@ export function defineColadaLoader<Data, isLazy extends boolean>(
parent?: DataLoaderEntryBase,
reload?: boolean
): Promise<void> {
const entries = router[
LOADER_ENTRIES_KEY
]! as unknown as _DefineLoaderEntryMap<
DataLoaderColadaEntry<boolean, unknown>
const entries = router[LOADER_ENTRIES_KEY]! as _DefineLoaderEntryMap<
DataLoaderColadaEntry<unknown>
>
const isSSR = router[IS_SSR_KEY]
const key = serializeQueryKey(options.key, to)
if (!entries.has(loader)) {
const route = shallowRef<RouteLocationNormalizedLoaded>(to)
entries.set(loader, {
// force the type to match
data: shallowRef<_DataMaybeLazy<Data, isLazy>>(),
data: shallowRef<Data | undefined>(),
isLoading: shallowRef(false),
error: shallowRef<any>(),
to,
Expand Down Expand Up @@ -308,7 +297,7 @@ export function defineColadaLoader<Data, isLazy extends boolean>(
}

function commit(
this: DataLoaderColadaEntry<isLazy, Data>,
this: DataLoaderColadaEntry<Data>,
to: RouteLocationNormalizedLoaded
) {
const key = serializeQueryKey(options.key, to)
Expand Down Expand Up @@ -358,7 +347,7 @@ export function defineColadaLoader<Data, isLazy extends boolean>(

// @ts-expect-error: requires the internals and symbol that are added later
const useDataLoader: // for ts
UseDataLoaderColada<isLazy, Data> = () => {
UseDataLoaderColada<Data> = () => {
// work with nested data loaders
const currentEntry = getCurrentContext()
const [parentEntry, _router, _route] = currentEntry
Expand All @@ -368,10 +357,8 @@ export function defineColadaLoader<Data, isLazy extends boolean>(

const entries = router[
LOADER_ENTRIES_KEY
]! as unknown as _DefineLoaderEntryMap<
DataLoaderColadaEntry<boolean, unknown>
>
let entry = entries.get(loader)
]! as unknown as _DefineLoaderEntryMap<DataLoaderColadaEntry<unknown>>
let entry = entries.get(loader) as DataLoaderColadaEntry<Data> | undefined

if (
// if the entry doesn't exist, create it with load and ensure it's loading
Expand All @@ -388,7 +375,7 @@ export function defineColadaLoader<Data, isLazy extends boolean>(
)
}

entry = entries.get(loader)!
entry = entries.get(loader)! as DataLoaderColadaEntry<Data>

// add ourselves to the parent entry children
if (parentEntry) {
Expand Down Expand Up @@ -450,7 +437,7 @@ export function defineColadaLoader<Data, isLazy extends boolean>(
asyncStatus: ext!.asyncStatus,
state: ext!.state,
isPending: ext!.isPending,
} satisfies UseDataLoaderColadaResult<boolean, unknown>
} satisfies UseDataLoaderColadaResult<Data>

// load ensures there is a pending load
const promise = entry
Expand Down Expand Up @@ -489,11 +476,10 @@ export function defineColadaLoader<Data, isLazy extends boolean>(
export const joinKeys = (keys: string[]): string => keys.join('|')

export interface DefineDataColadaLoaderOptions<
isLazy extends boolean,
Name extends keyof RouteMap,
Data,
> extends DefineDataLoaderOptionsBase<isLazy>,
Omit<UseQueryOptions<unknown>, 'query' | 'key'> {
> extends DefineDataLoaderOptionsBase,
Omit<UseQueryOptions<Data>, 'query' | 'key'> {
/**
* Key associated with the data and passed to pinia colada
* @param to - Route to load the data
Expand All @@ -517,8 +503,8 @@ export interface DefineDataColadaLoaderOptions<
*/
export interface DataColadaLoaderContext extends DataLoaderContextBase {}

export interface UseDataLoaderColadaResult<isLazy extends boolean, Data>
extends UseDataLoaderResult<isLazy, Data>,
export interface UseDataLoaderColadaResult<Data>
extends UseDataLoaderResult<Data>,
Pick<
UseQueryReturn<Data, any>,
'isPending' | 'refetch' | 'refresh' | 'status' | 'asyncStatus' | 'state'
Expand All @@ -527,8 +513,7 @@ export interface UseDataLoaderColadaResult<isLazy extends boolean, Data>
/**
* Data Loader composable returned by `defineColadaLoader()`.
*/
export interface UseDataLoaderColada<isLazy extends boolean, Data>
extends UseDataLoader<isLazy, Data> {
export interface UseDataLoaderColada<Data> extends UseDataLoader<Data> {
/**
* Data Loader composable returned by `defineColadaLoader()`.
*
Expand All @@ -555,12 +540,11 @@ export interface UseDataLoaderColada<isLazy extends boolean, Data>
// `return new NavigationResult()` in the loader
Exclude<Data, NavigationResult>,
// or use it as a composable
UseDataLoaderColadaResult<isLazy, Exclude<Data, NavigationResult>>
UseDataLoaderColadaResult<Exclude<Data, NavigationResult>>
>
}

export interface DataLoaderColadaEntry<isLazy extends boolean, Data>
extends DataLoaderEntryBase<isLazy, Data> {
export interface DataLoaderColadaEntry<Data> extends DataLoaderEntryBase<Data> {
/**
* Reactive route passed to pinia colada so it automatically refetch
*/
Expand Down Expand Up @@ -601,7 +585,7 @@ const DEFAULT_DEFINE_LOADER_OPTIONS = {
server: true,
commit: 'after-load',
} satisfies Omit<
DefineDataColadaLoaderOptions<boolean, keyof RouteMap, unknown>,
DefineDataColadaLoaderOptions<keyof RouteMap, unknown>,
'key' | 'query'
>

Expand All @@ -622,7 +606,7 @@ const toValueWithParameters = <T, Arg>(
* @param to - route to use
*/
function serializeQueryKey(
keyOption: DefineDataColadaLoaderOptions<boolean, string, unknown>['key'],
keyOption: DefineDataColadaLoaderOptions<string, unknown>['key'],
to: RouteLocationNormalizedLoaded
): string[] {
const key = toValueWithParameters(keyOption, to)
Expand Down
2 changes: 1 addition & 1 deletion src/data-loaders/defineLoader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { RouteLocationNormalizedLoaded } from 'vue-router'

function mockedLoader<T = string | NavigationResult>(
// boolean is easier to handle for router mock
options?: DefineDataLoaderOptions<boolean>
options?: DefineDataLoaderOptions
) {
const [spy, resolve, reject] = mockPromise<T, unknown>(
// not correct as T could be something else
Expand Down
Loading