chore: update libraries

This commit is contained in:
Shusui MOYATANI
2023-12-21 00:49:11 +09:00
parent 0eb7a554b4
commit 4df42cea0a
19 changed files with 3861 additions and 2063 deletions

5576
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,51 +26,51 @@
}, },
"devDependencies": { "devDependencies": {
"@types/jsdom": "^21.1.6", "@types/jsdom": "^21.1.6",
"@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.7.4", "@typescript-eslint/parser": "^6.15.0",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"eslint": "^8.51.0", "eslint": "^8.56.0",
"eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1", "eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.28.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-no-relative-import-paths": "^1.5.2", "eslint-plugin-no-relative-import-paths": "^1.5.3",
"eslint-plugin-solid": "^0.13.0", "eslint-plugin-solid": "^0.13.0",
"eslint-plugin-tailwindcss": "^3.13.0", "eslint-plugin-tailwindcss": "^3.13.0",
"husky": "^8.0.3", "husky": "^8.0.3",
"jsdom": "^23.0.0", "jsdom": "^23.0.1",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"lint-staged": "^14.0.1", "lint-staged": "^15.2.0",
"postcss": "^8.4.31", "postcss": "^8.4.32",
"prettier": "^3.0.3", "prettier": "^3.1.1",
"typescript": "^5.2.2", "typescript": "^5.3.3",
"vite": "^4.4.11", "vite": "^4.4.11",
"vite-plugin-solid": "^2.7.0", "vite-plugin-solid": "^2.8.0",
"vite-plugin-solid-svg": "^0.6.4", "vite-plugin-solid-svg": "^0.7.0",
"vitest": "^0.34.6" "vitest": "^1.1.0"
}, },
"dependencies": { "dependencies": {
"@solidjs/meta": "^0.28.6", "@solidjs/meta": "^0.29.3",
"@solidjs/router": "^0.8.3", "@solidjs/router": "^0.10.5",
"@tailwindcss/forms": "^0.5.6", "@tailwindcss/forms": "^0.5.7",
"@tanstack/query-async-storage-persister": "^4.36.1", "@tanstack/query-async-storage-persister": "^5.14.2",
"@tanstack/query-persist-client-core": "^4.36.1", "@tanstack/query-persist-client-core": "^5.14.2",
"@tanstack/solid-query": "^4.36.1", "@tanstack/solid-query": "^5.14.2",
"@tanstack/solid-virtual": "^3.0.0-beta.6", "@tanstack/solid-virtual": "^3.0.1",
"@textcomplete/core": "^0.1.12", "@textcomplete/core": "^0.1.13",
"@textcomplete/textarea": "^0.1.12", "@textcomplete/textarea": "^0.1.13",
"@thisbeyond/solid-dnd": "^0.7.4", "@thisbeyond/solid-dnd": "^0.7.5",
"@types/lodash": "^4.14.199", "@types/lodash": "^4.14.202",
"emoji-mart": "^5.5.2", "emoji-mart": "^5.5.2",
"heroicons": "^2.0.18", "heroicons": "^2.1.1",
"i18next": "^23.5.1", "i18next": "^23.7.11",
"i18next-browser-languagedetector": "^7.1.0", "i18next-browser-languagedetector": "^7.2.0",
"idb-keyval": "^6.2.1", "idb-keyval": "^6.2.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nostr-tools": "^1.16.0", "nostr-tools": "^1.16.0",
"solid-js": "^1.7.12", "solid-js": "^1.8.7",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.4.0",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"lint-staged": { "lint-staged": {

View File

@@ -1,6 +1,6 @@
import { createEffect, onCleanup, lazy, type Component } from 'solid-js'; import { createEffect, onCleanup, lazy, type Component } from 'solid-js';
import { Routes, Route } from '@solidjs/router'; import { HashRouter, Route } from '@solidjs/router';
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'; import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
import { persistQueryClient } from '@tanstack/query-persist-client-core'; import { persistQueryClient } from '@tanstack/query-persist-client-core';
import { QueryClient, QueryClientProvider } from '@tanstack/solid-query'; import { QueryClient, QueryClientProvider } from '@tanstack/solid-query';
@@ -50,12 +50,12 @@ const App: Component = () => {
return ( return (
<I18NextProvider i18next={i18next}> <I18NextProvider i18next={i18next}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<Routes> <HashRouter>
<Route path="/hello" element={<Hello />} /> <Route path="/hello" component={() => <Hello />} />
<Route path="/" element={<Home />} /> <Route path="/" component={() => <Home />} />
<Route path="/:id" element={<Permalink />} /> <Route path="/:id" component={() => <Permalink />} />
<Route path="/*" element={<NotFound />} /> <Route path="/*" component={() => <NotFound />} />
</Routes> </HashRouter>
</QueryClientProvider> </QueryClientProvider>
</I18NextProvider> </I18NextProvider>
); );

View File

@@ -117,13 +117,13 @@ const ReactionAction = (props: { event: NostrEvent }) => {
'text-zinc-400': !isReactedByMe() || isReactedByMeWithEmoji(), 'text-zinc-400': !isReactedByMe() || isReactedByMeWithEmoji(),
'hover:text-rose-400': !isReactedByMe() || isReactedByMeWithEmoji(), 'hover:text-rose-400': !isReactedByMe() || isReactedByMeWithEmoji(),
'text-rose-400': 'text-rose-400':
(isReactedByMe() && !isReactedByMeWithEmoji()) || publishReactionMutation.isLoading, (isReactedByMe() && !isReactedByMeWithEmoji()) || publishReactionMutation.isPending,
}} }}
> >
<button <button
class="h-4 w-4" class="h-4 w-4"
onClick={handleReaction} onClick={handleReaction}
disabled={publishReactionMutation.isLoading} disabled={publishReactionMutation.isPending}
> >
<Show when={isReactedByMe() && !isReactedByMeWithEmoji()} fallback={<HeartOutlined />}> <Show when={isReactedByMe() && !isReactedByMeWithEmoji()} fallback={<HeartOutlined />}>
<HeartSolid /> <HeartSolid />
@@ -140,7 +140,7 @@ const ReactionAction = (props: { event: NostrEvent }) => {
'text-zinc-400': !isReactedByMe() || !isReactedByMeWithEmoji(), 'text-zinc-400': !isReactedByMe() || !isReactedByMeWithEmoji(),
'hover:text-rose-400': !isReactedByMe() || !isReactedByMeWithEmoji(), 'hover:text-rose-400': !isReactedByMe() || !isReactedByMeWithEmoji(),
'text-rose-400': 'text-rose-400':
(isReactedByMe() && isReactedByMeWithEmoji()) || publishReactionMutation.isLoading, (isReactedByMe() && isReactedByMeWithEmoji()) || publishReactionMutation.isPending,
}} }}
> >
<EmojiPicker onEmojiSelect={handleEmojiSelect}> <EmojiPicker onEmojiSelect={handleEmojiSelect}>
@@ -199,10 +199,10 @@ const RepostAction = (props: { event: NostrEvent }) => {
classList={{ classList={{
'text-zinc-400': !isRepostedByMe(), 'text-zinc-400': !isRepostedByMe(),
'hover:text-green-400': !isRepostedByMe(), 'hover:text-green-400': !isRepostedByMe(),
'text-green-400': isRepostedByMe() || publishRepostMutation.isLoading, 'text-green-400': isRepostedByMe() || publishRepostMutation.isPending,
}} }}
> >
<button class="h-4 w-4" onClick={handleRepost} disabled={publishRepostMutation.isLoading}> <button class="h-4 w-4" onClick={handleRepost} disabled={publishRepostMutation.isPending}>
<ArrowPathRoundedSquare /> <ArrowPathRoundedSquare />
</button> </button>
<Show when={!config().hideCount && reposts().length > 0}> <Show when={!config().hideCount && reposts().length > 0}>
@@ -327,7 +327,7 @@ const Actions: Component<ActionProps> = (props) => {
const closeModal = () => setModal(null); const closeModal = () => setModal(null);
const deleteMutation = createMutation({ const deleteMutation = createMutation(() => ({
mutationKey: ['deleteEvent', props.event.id], mutationKey: ['deleteEvent', props.event.id],
mutationFn: (...params: Parameters<typeof commands.deleteEvent>) => mutationFn: (...params: Parameters<typeof commands.deleteEvent>) =>
commands commands
@@ -348,7 +348,7 @@ const Actions: Component<ActionProps> = (props) => {
onError: (err) => { onError: (err) => {
console.error('failed to delete', err); console.error('failed to delete', err);
}, },
}); }));
const menu: MenuItem[] = [ const menu: MenuItem[] = [
{ {

View File

@@ -122,8 +122,8 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
const replyTo = () => props.replyTo && textNote(props.replyTo); const replyTo = () => props.replyTo && textNote(props.replyTo);
const mode = () => props.mode ?? 'normal'; const mode = () => props.mode ?? 'normal';
const publishTextNoteMutation = createMutation({ const publishTextNoteMutation = createMutation(() => ({
mutationKey: ['publishTextNote'], mutationKey: ['publishTextNote'] as const,
mutationFn: commands.publishTextNote.bind(commands), mutationFn: commands.publishTextNote.bind(commands),
onSuccess: () => { onSuccess: () => {
console.log('succeeded to post'); console.log('succeeded to post');
@@ -133,7 +133,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
onError: (err) => { onError: (err) => {
console.error('error', err); console.error('error', err);
}, },
}); }));
const resizeTextArea = () => { const resizeTextArea = () => {
if (textAreaRef == null) return; if (textAreaRef == null) return;
@@ -141,8 +141,8 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
textAreaRef.style.height = `${textAreaRef.scrollHeight}px`; textAreaRef.style.height = `${textAreaRef.scrollHeight}px`;
}; };
const uploadFilesMutation = createMutation({ const uploadFilesMutation = createMutation(() => ({
mutationKey: ['uploadFiles'], mutationKey: ['uploadFiles'] as const,
mutationFn: async (files: File[]) => { mutationFn: async (files: File[]) => {
const uploadResults = await uploadFiles(uploadNostrBuild)(files); const uploadResults = await uploadFiles(uploadNostrBuild)(files);
const failed: File[] = []; const failed: File[] = [];
@@ -161,7 +161,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
window.alert(i18n()('posting.failedToUploadFile', { filenames })); window.alert(i18n()('posting.failedToUploadFile', { filenames }));
} }
}, },
}); }));
const taggedPubkeysWithoutMe = createMemo(() => { const taggedPubkeysWithoutMe = createMemo(() => {
const p = getPubkey(); const p = getPubkey();
@@ -197,7 +197,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
const submit = () => { const submit = () => {
if (text().length === 0) return; if (text().length === 0) return;
if (publishTextNoteMutation.isLoading) return; if (publishTextNoteMutation.isPending) return;
if (/nsec1[0-9a-zA-Z]+/.test(text())) { if (/nsec1[0-9a-zA-Z]+/.test(text())) {
window.alert(i18n()('posting.forbiddenToIncludeNsec')); window.alert(i18n()('posting.forbiddenToIncludeNsec'));
@@ -290,7 +290,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
*/ */
const handleChangeFile: JSX.EventHandler<HTMLInputElement, Event> = (ev) => { const handleChangeFile: JSX.EventHandler<HTMLInputElement, Event> = (ev) => {
ev.preventDefault(); ev.preventDefault();
if (uploadFilesMutation.isLoading) return; if (uploadFilesMutation.isPending) return;
// if (!ensureUploaderAgreement()) return; // if (!ensureUploaderAgreement()) return;
const files = [...(ev.currentTarget.files ?? [])]; const files = [...(ev.currentTarget.files ?? [])];
@@ -301,14 +301,14 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
const handleDrop: JSX.EventHandler<HTMLTextAreaElement, DragEvent> = (ev) => { const handleDrop: JSX.EventHandler<HTMLTextAreaElement, DragEvent> = (ev) => {
ev.preventDefault(); ev.preventDefault();
if (uploadFilesMutation.isLoading) return; if (uploadFilesMutation.isPending) return;
// if (!ensureUploaderAgreement()) return; // if (!ensureUploaderAgreement()) return;
const files = [...(ev?.dataTransfer?.files ?? [])]; const files = [...(ev?.dataTransfer?.files ?? [])];
uploadFilesMutation.mutate(files); uploadFilesMutation.mutate(files);
}; };
const handlePaste: JSX.EventHandler<HTMLTextAreaElement, ClipboardEvent> = (ev) => { const handlePaste: JSX.EventHandler<HTMLTextAreaElement, ClipboardEvent> = (ev) => {
if (uploadFilesMutation.isLoading) return; if (uploadFilesMutation.isPending) return;
const items = [...(ev?.clipboardData?.items ?? [])]; const items = [...(ev?.clipboardData?.items ?? [])];
@@ -333,10 +333,10 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
const submitDisabled = () => const submitDisabled = () =>
text().trim().length === 0 || text().trim().length === 0 ||
publishTextNoteMutation.isLoading || publishTextNoteMutation.isPending ||
uploadFilesMutation.isLoading; uploadFilesMutation.isPending;
const fileUploadDisabled = () => uploadFilesMutation.isLoading; const fileUploadDisabled = () => uploadFilesMutation.isPending;
onMount(() => { onMount(() => {
setTimeout(() => { setTimeout(() => {

View File

@@ -112,7 +112,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
return p != null && userFollowingPubkeys().includes(p); return p != null && userFollowingPubkeys().includes(p);
}; };
const updateContactsMutation = createMutation({ const updateContactsMutation = createMutation(() => ({
mutationKey: ['updateContacts'], mutationKey: ['updateContacts'],
mutationFn: (...params: Parameters<typeof commands.updateContacts>) => mutationFn: (...params: Parameters<typeof commands.updateContacts>) =>
commands commands
@@ -139,7 +139,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
.then(() => myFollowingQuery.refetch()) .then(() => myFollowingQuery.refetch())
.catch((err) => console.error('failed to refetch contacts', err)); .catch((err) => console.error('failed to refetch contacts', err));
}, },
}); }));
const updateContacts = async (op: 'follow' | 'unfollow', pubkey: string) => { const updateContacts = async (op: 'follow' | 'unfollow', pubkey: string) => {
try { try {
@@ -312,12 +312,12 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
{i18n()('profile.editProfile')} {i18n()('profile.editProfile')}
</button> </button>
</Match> </Match>
<Match when={updateContactsMutation.isLoading || updatingContacts()}> <Match when={updateContactsMutation.isPending || updatingContacts()}>
<span class="rounded-full border border-primary px-4 py-2 text-primary sm:text-base"> <span class="rounded-full border border-primary px-4 py-2 text-primary sm:text-base">
{i18n()('general.updating')} {i18n()('general.updating')}
</span> </span>
</Match> </Match>
<Match when={myFollowingQuery.isLoading || myFollowingQuery.isFetching}> <Match when={myFollowingQuery.isPending || myFollowingQuery.isFetching}>
<span class="rounded-full border border-primary px-4 py-2 text-primary sm:text-base"> <span class="rounded-full border border-primary px-4 py-2 text-primary sm:text-base">
{i18n()('general.loading')} {i18n()('general.loading')}
</span> </span>
@@ -329,7 +329,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
onMouseEnter={() => setHoverFollowButton(true)} onMouseEnter={() => setHoverFollowButton(true)}
onMouseLeave={() => setHoverFollowButton(false)} onMouseLeave={() => setHoverFollowButton(false)}
onClick={() => unfollow()} onClick={() => unfollow()}
disabled={updateContactsMutation.isLoading} disabled={updateContactsMutation.isPending}
> >
<Show when={!hoverFollowButton()} fallback={i18n()('profile.unfollow')}> <Show when={!hoverFollowButton()} fallback={i18n()('profile.unfollow')}>
{i18n()('profile.followingCurrently')} {i18n()('profile.followingCurrently')}
@@ -341,7 +341,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
class="w-28 rounded-full border border-primary px-4 py-2 text-primary class="w-28 rounded-full border border-primary px-4 py-2 text-primary
hover:border-rose-400 hover:text-rose-400" hover:border-rose-400 hover:text-rose-400"
onClick={() => follow()} onClick={() => follow()}
disabled={updateContactsMutation.isLoading} disabled={updateContactsMutation.isPending}
> >
{i18n()('profile.follow')} {i18n()('profile.follow')}
</button> </button>
@@ -357,7 +357,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
</ContextMenu> </ContextMenu>
</div> </div>
<Switch> <Switch>
<Match when={userFollowingQuery.isLoading}> <Match when={userFollowingQuery.isPending}>
<div class="shrink-0 text-xs">{i18n()('general.loading')}</div> <div class="shrink-0 text-xs">{i18n()('general.loading')}</div>
</Match> </Match>
<Match when={followed()}> <Match when={followed()}>
@@ -369,7 +369,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
</div> </div>
<div class="flex items-start px-4 pt-2"> <div class="flex items-start px-4 pt-2">
<div class="h-16 shrink overflow-hidden"> <div class="h-16 shrink overflow-hidden">
<Show when={profileQuery.isLoading}>{i18n()('general.loading')}</Show> <Show when={profileQuery.isPending}>{i18n()('general.loading')}</Show>
<Show when={(profile()?.display_name?.length ?? 0) > 0}> <Show when={(profile()?.display_name?.length ?? 0) > 0}>
<div class="truncate text-xl font-bold">{profile()?.display_name}</div> <div class="truncate text-xl font-bold">{profile()?.display_name}</div>
</Show> </Show>
@@ -387,7 +387,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
</span> </span>
} }
> >
<Match when={verificationQuery.isLoading}> <Match when={verificationQuery.isPending}>
<span class="inline-block h-3 w-3"> <span class="inline-block h-3 w-3">
<ArrowPath /> <ArrowPath />
</span> </span>

View File

@@ -50,7 +50,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
); );
const { updateProfile } = useCommands(); const { updateProfile } = useCommands();
const mutation = createMutation({ const mutation = createMutation(() => ({
mutationKey: ['updateProfile'], mutationKey: ['updateProfile'],
mutationFn: (...params: Parameters<typeof updateProfile>) => mutationFn: (...params: Parameters<typeof updateProfile>) =>
updateProfile(...params).then((promeses) => Promise.allSettled(promeses.map(timeout(10000)))), updateProfile(...params).then((promeses) => Promise.allSettled(promeses.map(timeout(10000)))),
@@ -73,12 +73,12 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
onError: (err) => { onError: (err) => {
console.error('failed to delete', err); console.error('failed to delete', err);
}, },
}); }));
const loading = () => query.isLoading || mutation.isLoading; const loading = () => query.isPending || mutation.isPending;
const disabled = () => loading(); const disabled = () => loading();
setInterval(() => console.log(query.isLoading, mutation.isLoading), 1000); setInterval(() => console.log(query.isPending, mutation.isPending), 1000);
const otherProperties = () => const otherProperties = () =>
omit(profile(), [ omit(profile(), [
@@ -313,7 +313,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
<button <button
type="submit" type="submit"
class="rounded bg-rose-300 p-2 font-bold text-white hover:bg-rose-400" class="rounded bg-rose-300 p-2 font-bold text-white hover:bg-rose-400"
disabled={mutation.isLoading} disabled={mutation.isPending}
> >
{i18n()('profile.edit.save')} {i18n()('profile.edit.save')}
</button> </button>
@@ -325,7 +325,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
{i18n()('profile.edit.cancel')} {i18n()('profile.edit.cancel')}
</button> </button>
</div> </div>
<Show when={mutation.isLoading}>{i18n()('profile.edit.updating')}</Show> <Show when={mutation.isPending}>{i18n()('profile.edit.updating')}</Show>
</form> </form>
</div> </div>
</BasicModal> </BasicModal>

View File

@@ -1,15 +1,7 @@
/* @refresh reload */ /* @refresh reload */
import { Router, hashIntegration } from '@solidjs/router';
import { render } from 'solid-js/web'; import { render } from 'solid-js/web';
import '@/index.css'; import '@/index.css';
import App from '@/App'; import App from '@/App';
render( render(() => <App />, document.getElementById('root') as HTMLElement);
() => (
<Router source={hashIntegration()}>
<App />
</Router>
),
document.getElementById('root') as HTMLElement,
);

View File

@@ -16,7 +16,7 @@ const useReactionMutation = (propsProvider: () => UseReactionMutationProps) => {
const commands = useCommands(); const commands = useCommands();
const mutation = createMutation({ const mutation = createMutation(() => ({
mutationKey: ['useReactionMutation', props().eventId] as const, mutationKey: ['useReactionMutation', props().eventId] as const,
mutationFn: (...params: Parameters<typeof commands.publishReaction>) => mutationFn: (...params: Parameters<typeof commands.publishReaction>) =>
commands commands
@@ -43,7 +43,7 @@ const useReactionMutation = (propsProvider: () => UseReactionMutationProps) => {
.then(() => queryClient.invalidateQueries({ queryKey })) .then(() => queryClient.invalidateQueries({ queryKey }))
.catch((err) => console.error('failed to refetch reactions', err)); .catch((err) => console.error('failed to refetch reactions', err));
}, },
}); }));
return mutation; return mutation;
}; };

View File

@@ -16,7 +16,7 @@ const useRepostMutation = (propsProvider: () => UseRepostMutationProps) => {
const commands = useCommands(); const commands = useCommands();
const mutation = createMutation({ const mutation = createMutation(() => ({
mutationKey: ['useRepostMutation', props().eventId] as const, mutationKey: ['useRepostMutation', props().eventId] as const,
mutationFn: (...params: Parameters<typeof commands.publishRepost>) => mutationFn: (...params: Parameters<typeof commands.publishRepost>) =>
commands commands
@@ -43,7 +43,7 @@ const useRepostMutation = (propsProvider: () => UseRepostMutationProps) => {
.then(() => queryClient.invalidateQueries({ queryKey })) .then(() => queryClient.invalidateQueries({ queryKey }))
.catch((err) => console.error('failed to refetch repost', err)); .catch((err) => console.error('failed to refetch repost', err));
}, },
}); }));
return mutation; return mutation;
}; };

View File

@@ -15,7 +15,6 @@ export const latestEventQuery =
queryClient: QueryClient; queryClient: QueryClient;
}) => }) =>
({ queryKey, signal }: { queryKey: K; signal?: AbortSignal }): Promise<NostrEvent | null> => { ({ queryKey, signal }: { queryKey: K; signal?: AbortSignal }): Promise<NostrEvent | null> => {
const prev = queryClient.getQueryData(queryKey, { stale: true }) as NostrEvent;
const task = taskProvider(queryKey); const task = taskProvider(queryKey);
if (task == null) return Promise.resolve(null); if (task == null) return Promise.resolve(null);
const promise = task.firstEventPromise().catch(() => { const promise = task.firstEventPromise().catch(() => {
@@ -23,9 +22,12 @@ export const latestEventQuery =
}); });
task.onUpdate((events) => { task.onUpdate((events) => {
const latest = pickLatestEvent(events); const latest = pickLatestEvent(events);
if (prev == null || (latest != null && compareEvents(latest, prev) >= 0)) { queryClient.setQueriesData<NostrEvent>({ queryKey, stale: true }, (prev) => {
queryClient.setQueryData(queryKey, latest); if (latest != null && (prev == null || compareEvents(latest, prev) >= 0)) {
return latest;
} }
return undefined;
});
}); });
registerTask({ task, signal }); registerTask({ task, signal });
return timeout(15000, `${JSON.stringify(queryKey)}`)(promise); return timeout(15000, `${JSON.stringify(queryKey)}`)(promise);
@@ -40,13 +42,12 @@ export const eventsQuery =
queryClient: QueryClient; queryClient: QueryClient;
}) => }) =>
({ queryKey, signal }: { queryKey: K; signal?: AbortSignal }): Promise<NostrEvent[]> => { ({ queryKey, signal }: { queryKey: K; signal?: AbortSignal }): Promise<NostrEvent[]> => {
const prev = queryClient.getQueryData(queryKey, { stale: true }) as NostrEvent[];
const task = taskProvider(queryKey); const task = taskProvider(queryKey);
if (task == null) return Promise.resolve([]); if (task == null) return Promise.resolve([]);
const promise = task.toUpdatePromise().catch(() => []); const promise = task.toUpdatePromise().catch(() => []);
task.onUpdate((events) => { task.onUpdate((events) => {
// TODO consider kind:5 deletion // TODO consider kind:5 deletion
queryClient.setQueryData(queryKey, () => { queryClient.setQueriesData<NostrEvent[]>({ queryKey, stale: true }, (prev) => {
if (prev == null) return events; if (prev == null) return events;
const deduped = uniqBy([...prev, ...events], (e) => e.id); const deduped = uniqBy([...prev, ...events], (e) => e.id);
return sortEvents(deduped); return sortEvents(deduped);

View File

@@ -18,9 +18,9 @@ export type UseEvent = {
const useEvent = (propsProvider: () => UseEventProps | null): UseEvent => { const useEvent = (propsProvider: () => UseEventProps | null): UseEvent => {
const props = createMemo(propsProvider); const props = createMemo(propsProvider);
const query = createQuery( const query = createQuery(() => ({
() => ['useEvent', props()] as const, queryKey: ['useEvent', props()] as const,
({ queryKey, signal }) => { queryFn: ({ queryKey, signal }) => {
const [, currentProps] = queryKey; const [, currentProps] = queryKey;
if (currentProps == null) return null; if (currentProps == null) return null;
const { eventId } = currentProps; const { eventId } = currentProps;
@@ -31,15 +31,13 @@ const useEvent = (propsProvider: () => UseEventProps | null): UseEvent => {
registerTask({ task, signal }); registerTask({ task, signal });
return timeout(15000, `useEvent: ${eventId}`)(promise); return timeout(15000, `useEvent: ${eventId}`)(promise);
}, },
{
// Text notes never change, so they can be stored for a long time. // Text notes never change, so they can be stored for a long time.
// However, events tend to be unreferenced as time passes. // However, events tend to be unreferenced as time passes.
staleTime: 4 * 60 * 60 * 1000, // 4 hour staleTime: 4 * 60 * 60 * 1000, // 4 hour
cacheTime: 4 * 60 * 60 * 1000, // 4 hour cacheTime: 4 * 60 * 60 * 1000, // 4 hour
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
refetchOnMount: false, refetchOnMount: false,
}, }));
);
const event = () => query.data ?? null; const event = () => query.data ?? null;

View File

@@ -68,9 +68,9 @@ const useFollowings = (propsProvider: () => UseFollowingsProps | null): UseFollo
const props = createMemo(propsProvider); const props = createMemo(propsProvider);
const genQueryKey = () => ['useFollowings', props()] as const; const genQueryKey = () => ['useFollowings', props()] as const;
const query = createQuery( const query = createQuery(() => ({
genQueryKey, queryKey: genQueryKey(),
latestEventQuery({ queryFn: latestEventQuery<ReturnType<typeof genQueryKey>>({
taskProvider: ([, currentProps]) => { taskProvider: ([, currentProps]) => {
if (currentProps == null) return null; if (currentProps == null) return null;
const { pubkey } = currentProps; const { pubkey } = currentProps;
@@ -78,17 +78,16 @@ const useFollowings = (propsProvider: () => UseFollowingsProps | null): UseFollo
}, },
queryClient, queryClient,
}), }),
{
staleTime: 5 * 60 * 1000, // 5 min staleTime: 5 * 60 * 1000, // 5 min
cacheTime: 3 * 24 * 60 * 60 * 1000, // 3 days cacheTime: 3 * 24 * 60 * 60 * 1000, // 3 days
refetchOnMount: true, refetchOnMount: true,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
refetchOnReconnect: false, refetchOnReconnect: false,
refetchInterval: 0, refetchInterval: 0,
}, }));
);
const invalidateFollowings = (): Promise<void> => queryClient.invalidateQueries(genQueryKey()); const invalidateFollowings = (): Promise<void> =>
queryClient.invalidateQueries({ queryKey: genQueryKey() });
return { ...buildMethods(() => query.data), invalidateFollowings, query }; return { ...buildMethods(() => query.data), invalidateFollowings, query };
}; };

View File

@@ -30,9 +30,9 @@ const useParameterizedReplaceableEvent = (
const props = createMemo(propsProvider); const props = createMemo(propsProvider);
const genQueryKey = () => ['useFollowings', props()] as const; const genQueryKey = () => ['useFollowings', props()] as const;
const query = createQuery( const query = createQuery(() => ({
genQueryKey, queryKey: genQueryKey(),
({ queryKey, signal }) => { queryFn: ({ queryKey, signal }) => {
console.debug('useFollowings'); console.debug('useFollowings');
const [, currentProps] = queryKey; const [, currentProps] = queryKey;
if (currentProps == null) return Promise.resolve(null); if (currentProps == null) return Promise.resolve(null);
@@ -59,11 +59,9 @@ const useParameterizedReplaceableEvent = (
`useParameterizedReplaceableEvent: ${kind}:${author}:${identifier}`, `useParameterizedReplaceableEvent: ${kind}:${author}:${identifier}`,
)(promise); )(promise);
}, },
{
staleTime: 5 * 60 * 1000, // 5 min staleTime: 5 * 60 * 1000, // 5 min
cacheTime: 4 * 60 * 60 * 1000, // 4 hour cacheTime: 4 * 60 * 60 * 1000, // 4 hour
}, }));
);
const event = () => query.data ?? null; const event = () => query.data ?? null;

View File

@@ -27,16 +27,14 @@ export type UseProfiles = {
queries: CreateQueryResult<NostrEvent | null>[]; queries: CreateQueryResult<NostrEvent | null>[];
}; };
type UseProfileQueryKey = readonly ['useProfile', UseProfileProps | null];
const useProfile = (propsProvider: () => UseProfileProps | null): UseProfile => { const useProfile = (propsProvider: () => UseProfileProps | null): UseProfile => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const props = createMemo(propsProvider); const props = createMemo(propsProvider);
const genQueryKey = createMemo((): UseProfileQueryKey => ['useProfile', props()] as const); const genQueryKey = createMemo(() => ['useProfile', props()] as const);
const query = createQuery( const query = createQuery(() => ({
genQueryKey, queryKey: genQueryKey(),
latestEventQuery({ queryFn: latestEventQuery<ReturnType<typeof genQueryKey>>({
taskProvider: ([, currentProps]) => { taskProvider: ([, currentProps]) => {
if (currentProps == null) return null; if (currentProps == null) return null;
const { pubkey } = currentProps; const { pubkey } = currentProps;
@@ -44,15 +42,13 @@ const useProfile = (propsProvider: () => UseProfileProps | null): UseProfile =>
}, },
queryClient, queryClient,
}), }),
{
// Profiles are updated occasionally, so a short staleTime is used here. // Profiles are updated occasionally, so a short staleTime is used here.
// cacheTime is long so that the user see profiles instantly. // cacheTime is long so that the user see profiles instantly.
staleTime: 5 * 60 * 1000, // 5 min staleTime: 5 * 60 * 1000, // 5 min
cacheTime: 3 * 24 * 60 * 60 * 1000, // 3 days cacheTime: 3 * 24 * 60 * 60 * 1000, // 3 days
refetchInterval: 5 * 60 * 1000, // 5 min refetchInterval: 5 * 60 * 1000, // 5 min
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
}, }));
);
const event = () => query.data; const event = () => query.data;
@@ -62,7 +58,8 @@ const useProfile = (propsProvider: () => UseProfileProps | null): UseProfile =>
return safeParseProfile(content); return safeParseProfile(content);
}); });
const invalidateProfile = (): Promise<void> => queryClient.invalidateQueries(genQueryKey()); const invalidateProfile = (): Promise<void> =>
queryClient.invalidateQueries({ queryKey: genQueryKey() });
return { profile, event, invalidateProfile, query }; return { profile, event, invalidateProfile, query };
}; };

View File

@@ -30,9 +30,9 @@ const useReactions = (propsProvider: () => UseReactionsProps | null): UseReactio
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const genQueryKey = createMemo(() => queryKeyUseReactions(propsProvider())); const genQueryKey = createMemo(() => queryKeyUseReactions(propsProvider()));
const query = createQuery( const query = createQuery(() => ({
genQueryKey, queryKey: genQueryKey(),
eventsQuery({ queryFn: eventsQuery<ReturnType<typeof queryKeyUseReactions>>({
taskProvider: ([, currentProps]) => { taskProvider: ([, currentProps]) => {
if (currentProps == null) return null; if (currentProps == null) return null;
const { eventId: mentionedEventId } = currentProps; const { eventId: mentionedEventId } = currentProps;
@@ -40,12 +40,10 @@ const useReactions = (propsProvider: () => UseReactionsProps | null): UseReactio
}, },
queryClient, queryClient,
}), }),
{
staleTime: 1 * 60 * 1000, // 1 min staleTime: 1 * 60 * 1000, // 1 min
cacheTime: 4 * 60 * 60 * 1000, // 4 hour cacheTime: 4 * 60 * 60 * 1000, // 4 hour
refetchInterval: 1 * 60 * 1000, // 1 min refetchInterval: 1 * 60 * 1000, // 1 min
}, }));
);
const reactions = () => { const reactions = () => {
const data = query.data ?? []; const data = query.data ?? [];

View File

@@ -25,9 +25,9 @@ const useReposts = (propsProvider: () => UseRepostsProps): UseReposts => {
const props = createMemo(propsProvider); const props = createMemo(propsProvider);
const genQueryKey = createMemo(() => queryKeyUseReposts(props())); const genQueryKey = createMemo(() => queryKeyUseReposts(props()));
const query = createQuery( const query = createQuery(() => ({
genQueryKey, queryKey: genQueryKey(),
eventsQuery({ queryFn: eventsQuery<ReturnType<typeof genQueryKey>>({
taskProvider: ([, currentProps]) => { taskProvider: ([, currentProps]) => {
if (currentProps == null) return null; if (currentProps == null) return null;
const { eventId: mentionedEventId } = currentProps; const { eventId: mentionedEventId } = currentProps;
@@ -35,12 +35,10 @@ const useReposts = (propsProvider: () => UseRepostsProps): UseReposts => {
}, },
queryClient, queryClient,
}), }),
{
staleTime: 1 * 60 * 1000, // 1 min staleTime: 1 * 60 * 1000, // 1 min
cacheTime: 4 * 60 * 60 * 1000, // 4 hour cacheTime: 4 * 60 * 60 * 1000, // 4 hour
refetchInterval: 1 * 60 * 1000, // 1 min refetchInterval: 1 * 60 * 1000, // 1 min
}, }));
);
const reposts = () => { const reposts = () => {
const data = query.data ?? []; const data = query.data ?? [];

View File

@@ -14,19 +14,19 @@ export type UseVerification = {
const useVerification = (propsProvider: () => UseVerificationProps | null): UseVerification => { const useVerification = (propsProvider: () => UseVerificationProps | null): UseVerification => {
const props = createMemo(propsProvider); const props = createMemo(propsProvider);
const query = createQuery( const genQueryKey = () => ['useVerification', props()] as const;
() => ['useVerification', props()] as const,
({ queryKey }) => { const query = createQuery(() => ({
queryKey: genQueryKey(),
queryFn: ({ queryKey }) => {
const [, currentProps] = queryKey; const [, currentProps] = queryKey;
if (currentProps == null) return Promise.resolve(null); if (currentProps == null) return Promise.resolve(null);
const { nip05: nip05string } = currentProps; const { nip05: nip05string } = currentProps;
return nip05.queryProfile(nip05string); return nip05.queryProfile(nip05string);
}, },
{
staleTime: 30 * 60 * 1000, // 30 min staleTime: 30 * 60 * 1000, // 30 min
cacheTime: 24 * 60 * 60 * 1000, // 24 hour cacheTime: 24 * 60 * 60 * 1000, // 24 hour
}, }));
);
const verification = () => query?.data ?? null; const verification = () => query?.data ?? null;

View File

@@ -56,12 +56,15 @@ export type UseOgpProps = {
export const useOgp = (propsProvider: () => UseOgpProps) => { export const useOgp = (propsProvider: () => UseOgpProps) => {
const genQueryKey = () => ['useOgp', propsProvider().url] as const; const genQueryKey = () => ['useOgp', propsProvider().url] as const;
const query = createQuery(genQueryKey, ({ queryKey: [, url] }) => fetchOgpContent(url), {
const query = createQuery(() => ({
queryKey: genQueryKey(),
queryFn: ({ queryKey: [, url] }) => fetchOgpContent(url),
staleTime: 4 * 60 * 60 * 1000, // 4 hour staleTime: 4 * 60 * 60 * 1000, // 4 hour
cacheTime: 4 * 60 * 60 * 1000, // 4 hour cacheTime: 4 * 60 * 60 * 1000, // 4 hour
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
refetchOnMount: false, refetchOnMount: false,
}); }));
const ogp = () => query.data; const ogp = () => query.data;