Skip to content

feat: ensure wraps only promise-returning functions and handles non-promise returns #1

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

Merged
merged 4 commits into from
Nov 23, 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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ pnpm-debug.log*

# Idea/VSCode settings
.idea/
.vscode/
.vscode/*
!.vscode/settings.json

# Miscellaneous
*.tgz
10 changes: 10 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"singleQuote": true,
"vueIndentScriptAndStyle": true,
"singleAttributePerLine": true,
"htmlWhitespaceSensitivity": "strict",
"arrowParens": "avoid",
"bracketSameLine": true,
"jsxSingleQuote": true,
"proseWrap": "always"
}
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nuxt-use-async-data-wrapper",
"version": "1.0.1",
"version": "1.1.0",
"description": "A utility to wrap Promise-returning functions with useAsyncData for Nuxt",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand All @@ -25,10 +25,11 @@
"url": "https://github.com/leynier/nuxt-use-async-data-wrapper.git"
},
"peerDependencies": {
"vue": "^3.0.0",
"nuxt": "^3.0.0"
"nuxt": "^3.0.0",
"vue": "^3.0.0"
},
"devDependencies": {
"prettier": "^3.3.3",
"typescript": "^5.7.2",
"vue": "^3.5.13"
},
Expand Down
10 changes: 10 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 34 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ type AsyncDataResult<T> = AsyncData<T, Error>;
/**
* Transforms an object's Promise-returning functions into functions compatible with useAsyncData.
*
* Only includes functions that return Promises; other properties are excluded.
*
* For each function in the object:
* - If the function returns a Promise and takes no arguments, it becomes a function that accepts optional AsyncDataOptions.
* - If the function returns a Promise and takes arguments, it becomes a function that accepts an argsSupplier and optional AsyncDataOptions.
*
* This allows you to use the functions within a Nuxt application, leveraging the reactivity and data fetching capabilities of useAsyncData.
*
* @template T - The type of the object.
*/
export type AsyncDataWrapper<T> = {
[K in keyof T]: T[K] extends (...args: infer Args) => Promise<infer R>
[K in keyof T as T[K] extends (...args: any[]) => Promise<any>
? K
: never]: T[K] extends (...args: infer Args) => Promise<infer R>
? Args extends []
? /**
* Functions without arguments.
Expand All @@ -33,7 +35,10 @@ export type AsyncDataWrapper<T> = {
* @param options - Optional AsyncDataOptions to configure useAsyncData.
* @returns AsyncDataResult containing the data, pending state, and error.
*/
(argsSupplier: () => Args, options?: AsyncDataOptions<R>) => AsyncDataResult<R>
(
argsSupplier: () => Args,
options?: AsyncDataOptions<R>,
) => AsyncDataResult<R>
: never;
};

Expand All @@ -56,13 +61,15 @@ export type AsyncDataWrapper<T> = {
* const wrappedObject = useAsyncDataWrapper(originalObject);
* ```
*/
export function useAsyncDataWrapper<T extends Record<string, any>>(obj: T): AsyncDataWrapper<T> {
export function useAsyncDataWrapper<T extends Record<string, any>>(
obj: T,
): AsyncDataWrapper<T> {
const composable = {} as AsyncDataWrapper<T>;
const proto = Object.getPrototypeOf(obj);

// Get function names from the object's prototype, excluding the constructor
const functionNames = Object.getOwnPropertyNames(proto).filter(
key => key !== 'constructor' && typeof obj[key] === 'function'
key => key !== 'constructor' && typeof obj[key] === 'function',
);

for (const key of functionNames) {
Expand All @@ -89,27 +96,41 @@ export function useAsyncDataWrapper<T extends Record<string, any>>(obj: T): Asyn
// Reactive reference to arguments
const argsRef = computed(() => argsSupplier!());
// Unique key for useAsyncData
const dataKeyRef = computed(() => `${key}-${JSON.stringify(argsRef.value)}`);
const dataKeyRef = computed(
() => `${key}-${JSON.stringify(argsRef.value)}`,
);

// Call useAsyncData with the generated key and function
const asyncDataResult = useAsyncData(
dataKeyRef.value,
() => originalFunction(...unref(argsRef)),
() => {
const result = originalFunction(...unref(argsRef));
// Ensure we return a Promise
return result instanceof Promise ? result : Promise.resolve(result);
},
{
// Re-execute when arguments change
watch: [argsRef],
// Spread additional options
...options,
}
},
);

return asyncDataResult;
} else {
// For functions without arguments
const asyncDataResult = useAsyncData(key, () => originalFunction(), {
// Spread additional options
...options,
});
const asyncDataResult = useAsyncData(
key,
() => {
const result = originalFunction();
// Ensure we return a Promise
return result instanceof Promise ? result : Promise.resolve(result);
},
{
// Spread additional options
...options,
},
);

return asyncDataResult;
}
Expand Down