From 00d6350cee5158796947692f5cae259ff0e4168a Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sat, 7 Mar 2026 03:07:40 +0100 Subject: [PATCH] Optionally include client data during manga migration --- CHANGELOG.md | 1 + src/features/chapter/services/Chapters.ts | 10 +- src/features/manga/services/Mangas.ts | 75 +++++- src/features/metadata/Metadata.constants.ts | 14 ++ .../metadata/services/MetadataUpdater.ts | 34 +-- src/features/migration/Migration.types.ts | 1 + .../migration/components/MigrateDialog.tsx | 9 +- src/features/settings/Settings.constants.ts | 1 + src/i18n/locales/en.po | 1 + src/lib/graphql/category/CategoryMutation.ts | 13 +- src/lib/graphql/chapter/ChapterMutation.ts | 13 +- src/lib/graphql/generated/graphql.ts | 43 ++-- src/lib/graphql/manga/MangaMutation.ts | 13 +- src/lib/graphql/manga/MangaQuery.ts | 12 + .../metadata/GlobalMetadataMutation.ts | 13 +- src/lib/graphql/source/SourceMutation.ts | 13 +- src/lib/requests/RequestManager.ts | 213 +++++++++++++++--- 17 files changed, 382 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0f2afb2..42580a1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - (**Theme**) Add an option to save the dynamic color theme on the manga page as a custom theme - (**WebUI Update**) Add an option to (partially) disable showing information when the webUI got updated - (**Server Update**) Add an option to disable showing information when the server got updated +- (**Manga**) Add an option to include client data during migration ### Changed - (**General**) Preserve refresh token (UI Login) over sessions diff --git a/src/features/chapter/services/Chapters.ts b/src/features/chapter/services/Chapters.ts index 9905d2db..2712d855 100644 --- a/src/features/chapter/services/Chapters.ts +++ b/src/features/chapter/services/Chapters.ts @@ -172,10 +172,10 @@ export class Chapters { return chapters.filter((chapter) => !Chapters.isRead(chapter)); } - static getMatchingChapterNumberChapters( - chaptersA: Chapter[], - chaptersB: Chapter[], - ): [ChapterA: Chapter, ChapterB: Chapter][] { + static getMatchingChapterNumberChapters( + chaptersA: ChapterA[], + chaptersB: ChapterB[], + ): [ChapterA: ChapterA, ChapterB: ChapterB][] { return chaptersA .map((chapterA) => { const matchingChapter = chaptersB.find((chapterB) => chapterA.chapterNumber === chapterB.chapterNumber); @@ -186,7 +186,7 @@ export class Chapters { return [chapterA, matchingChapter]; }) - .filter((matchingChapters): matchingChapters is [Chapter, Chapter] => matchingChapters !== null); + .filter((matchingChapters): matchingChapters is [ChapterA, ChapterB] => matchingChapters !== null); } static async download(chapterIds: number[], disableConfirmation?: boolean): Promise { diff --git a/src/features/manga/services/Mangas.ts b/src/features/manga/services/Mangas.ts index c8ceb8e8..473c97ca 100644 --- a/src/features/manga/services/Mangas.ts +++ b/src/features/manga/services/Mangas.ts @@ -18,6 +18,7 @@ import { GetMangaToMigrateQuery, GetMangaToMigrateToFetchMutation, MangaBaseFieldsFragment, + SetChapterMetasItemInput, UpdateMangaCategoriesPatchInput, } from '@/lib/graphql/generated/graphql.ts'; import { Chapters } from '@/features/chapter/services/Chapters.ts'; @@ -52,6 +53,7 @@ import { getErrorMessage } from '@/lib/HelperFunctions.ts'; import { assertIsDefined } from '@/base/Asserts.ts'; import { Confirmation } from '@/base/AppAwaitableComponent.ts'; import { UrlUtil } from '@/lib/UrlUtil.ts'; +import { ALL_APP_METADATA_KEY_PREFIXES } from '@/features/metadata/Metadata.constants.ts'; type MangaToMigrate = NonNullable; type MangaToMigrateTo = NonNullable['manga']; @@ -317,6 +319,7 @@ export class Mangas { mangaToMigrate: GetMangaToMigrateQuery['manga'], mangaToMigrateToInfo: GetMangaToMigrateToFetchMutation, deleteChapters: boolean, + migrateMetadata: boolean, ): MigrateAction { if (!mangaToMigrate.chapters || !mangaToMigrateToInfo.fetchChapters?.chapters) { throw new Error('Chapters are missing'); @@ -330,8 +333,11 @@ export class Mangas { const readChapters: number[] = []; const bookmarkedChapters: number[] = []; + const chapterPreDeleteIds: number[] = []; + const chapterUpdateItems: SetChapterMetasItemInput[] = []; + migratableChapters.forEach(([chapterToMigrate, chapterToMigrateTo]) => { - const { isRead, isBookmarked } = chapterToMigrate; + const { isRead, isBookmarked, meta } = chapterToMigrate; if (isRead) { readChapters.push(chapterToMigrateTo.id); @@ -340,6 +346,14 @@ export class Mangas { if (isBookmarked) { bookmarkedChapters.push(chapterToMigrateTo.id); } + + if (migrateMetadata && meta?.length) { + chapterPreDeleteIds.push(chapterToMigrateTo.id); + chapterUpdateItems.push({ + chapterIds: [chapterToMigrateTo.id], + metas: meta.map(({ key, value }) => ({ key, value })), + }); + } }); return { @@ -348,6 +362,18 @@ export class Mangas { readChapters.length && requestManager.updateChapters(readChapters, { isRead: true }).response, bookmarkedChapters.length && requestManager.updateChapters(bookmarkedChapters, { isBookmarked: true }).response, + chapterUpdateItems.length && + requestManager.updateChapterMeta({ + preUpdateDeleteInput: { + items: [ + { + chapterIds: chapterPreDeleteIds, + prefixes: ALL_APP_METADATA_KEY_PREFIXES, + }, + ], + }, + updateInput: { items: chapterUpdateItems }, + }).response, ].filter((promise) => !!promise), cleanup: () => mode === 'migrate' @@ -407,22 +433,45 @@ export class Mangas { mangaToMigrateTo: MangaToMigrateTo, migrateCategories: boolean, removeMangaFromCategories: boolean, + migrateMetadata: boolean, ): MigrateAction { if (migrateCategories && !mangaToMigrateFrom?.categories) { throw new Error('Categories are missing'); } + const mangaMeta = migrateMetadata ? mangaToMigrateFrom.meta : undefined; + return { - copy: () => [ - requestManager.updateManga(mangaToMigrateTo.id, { - updateManga: { inLibrary: true }, - updateMangaCategories: migrateCategories - ? { - addToCategories: mangaToMigrateFrom.categories?.nodes.map((category) => category.id), - } - : undefined, - }).response, - ], + copy: () => + [ + requestManager.updateManga(mangaToMigrateTo.id, { + updateManga: { inLibrary: true }, + updateMangaCategories: migrateCategories + ? { + addToCategories: mangaToMigrateFrom.categories?.nodes.map((category) => category.id), + } + : undefined, + }).response, + mangaMeta?.length && + requestManager.updateMangaMeta({ + preUpdateDeleteInput: { + items: [ + { + mangaIds: [mangaToMigrateTo.id], + prefixes: ALL_APP_METADATA_KEY_PREFIXES, + }, + ], + }, + updateInput: { + items: [ + { + mangaIds: [mangaToMigrateTo.id], + metas: mangaMeta.map(({ key, value }) => ({ key, value })), + }, + ], + }, + }).response, + ].filter((promise) => !!promise), cleanup: () => mode === 'migrate' ? [ @@ -444,6 +493,7 @@ export class Mangas { migrateCategories, migrateTracking, deleteChapters, + migrateMetadata, }: Omit, disableConfirmation?: boolean, ): Promise { @@ -458,6 +508,7 @@ export class Mangas { migrateCategories, migrateTracking, deleteChapters, + migrateMetadata, }).response, requestManager.getMangaToMigrateToFetch(mangaIdToMigrateTo, { migrateChapters, @@ -511,6 +562,7 @@ export class Mangas { mangaToMigrateData.manga, mangaToMigrateToData, !!deleteChapters, + !!migrateMetadata, ), ], [ @@ -531,6 +583,7 @@ export class Mangas { mangaToMigrateToData.fetchManga!.manga, !!migrateCategories, removeMangaFromCategories, + !!migrateMetadata, ), ], ); diff --git a/src/features/metadata/Metadata.constants.ts b/src/features/metadata/Metadata.constants.ts index 97cab0b4..d2921c1d 100644 --- a/src/features/metadata/Metadata.constants.ts +++ b/src/features/metadata/Metadata.constants.ts @@ -120,6 +120,9 @@ export const APP_METADATA: Record< deleteChapters: { convert: convertToBoolean, }, + migrateMetadata: { + convert: convertToBoolean, + }, migrateSortSettings: { convert: convertToObject, }, @@ -437,6 +440,7 @@ export const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [ 'migrateCategories', 'migrateTracking', 'deleteChapters', + 'migrateMetadata', 'migrateSortSettings', // browse @@ -696,3 +700,13 @@ export const METADATA_MIGRATIONS: IMetadataMigration[] = [ deleteKeys: ['sourceLanguages', 'extensionLanguages'], }, ]; + +export const ALL_APP_METADATA_KEY_PREFIXES: string[] = [ + ...METADATA_MIGRATIONS.reduce>((acc, migration) => { + if (migration.appKeyPrefix?.oldPrefix) { + acc.add(migration.appKeyPrefix.oldPrefix); + } + return acc; + }, new Set()), + APP_METADATA_KEY_PREFIX, +]; diff --git a/src/features/metadata/services/MetadataUpdater.ts b/src/features/metadata/services/MetadataUpdater.ts index 8ec0528c..1ce1a86b 100644 --- a/src/features/metadata/services/MetadataUpdater.ts +++ b/src/features/metadata/services/MetadataUpdater.ts @@ -39,7 +39,7 @@ type MetadataUpdateOptions = { type ProcessedEntityMetadata = { updateMetas: MetaInput[]; - deleteKeys: string[]; + postUpdateDeleteKeys: string[]; migrateMetas: MetaInput[]; }; @@ -98,7 +98,7 @@ const processEntityMetadata = ( value: `${value}`, })); - return { updateMetas: allUpdateMetas, deleteKeys: uniqueDeleteKeys, migrateMetas }; + return { updateMetas: allUpdateMetas, postUpdateDeleteKeys: uniqueDeleteKeys, migrateMetas }; }; type ProcessedEntry = ProcessedEntityMetadata & { metadataHolder: GqlMetaHolder }; @@ -107,11 +107,11 @@ const groupByIdenticalMetas = ( processed: Array, ): { updateGroups: Array<{ ids: Id[]; metas: MetaInput[] }>; - deleteGroups: Array<{ ids: Id[]; keys: string[] }>; + postUpdateDeleteGroups: Array<{ ids: Id[]; keys: string[] }>; migrateGroups: Array<{ ids: Id[]; metas: MetaInput[] }>; } => { const updateMap = new Map(); - const deleteMap = new Map(); + const postUpdateDeleteMap = new Map(); const migrateMap = new Map(); for (const entry of processed) { @@ -127,13 +127,13 @@ const groupByIdenticalMetas = ( } } - if (entry.deleteKeys.length > 0) { - const key = JSON.stringify(entry.deleteKeys); - const existing = deleteMap.get(key); + if (entry.postUpdateDeleteKeys.length > 0) { + const key = JSON.stringify(entry.postUpdateDeleteKeys); + const existing = postUpdateDeleteMap.get(key); if (existing) { existing.ids.push(id); } else { - deleteMap.set(key, { ids: [id], keys: entry.deleteKeys }); + postUpdateDeleteMap.set(key, { ids: [id], keys: entry.postUpdateDeleteKeys }); } } @@ -150,7 +150,7 @@ const groupByIdenticalMetas = ( return { updateGroups: [...updateMap.values()], - deleteGroups: [...deleteMap.values()], + postUpdateDeleteGroups: [...postUpdateDeleteMap.values()], migrateGroups: [...migrateMap.values()], }; }; @@ -159,18 +159,21 @@ const createEntityMetaInput = ( processed: ProcessedEntry[], idKey: Key, ) => { - const { updateGroups, deleteGroups, migrateGroups } = groupByIdenticalMetas( + const { updateGroups, postUpdateDeleteGroups, migrateGroups } = groupByIdenticalMetas( processed as Array, ); return { + preUpdateDeleteInput: { + items: [], + }, updateInput: { items: updateGroups.map( ({ ids, metas }) => ({ [idKey]: ids, metas }) as Record & { metas: MetaInput[] }, ), }, - deleteInput: { - items: deleteGroups.map( + postUpdateDeleteInput: { + items: postUpdateDeleteGroups.map( ({ ids, keys }) => ({ [idKey]: ids, keys }) as Record & { keys: string[] }, ), }, @@ -198,12 +201,15 @@ const requestBatchMetadataUpdate = async ( switch (holderType) { case 'global': { const withUpdates = processed.filter(({ updateMetas }) => updateMetas.length > 0); - const withDeletes = processed.filter(({ deleteKeys }) => deleteKeys.length > 0); + const withDeletes = processed.filter(({ postUpdateDeleteKeys }) => postUpdateDeleteKeys.length > 0); const withMigrations = processed.filter(({ migrateMetas }) => migrateMetas.length > 0); await requestManager.updateGlobalMeta({ + preUpdateDeleteInput: { keys: [] }, updateInput: { metas: withUpdates.flatMap(({ updateMetas }) => updateMetas) }, - deleteInput: { keys: withDeletes.flatMap(({ deleteKeys }) => deleteKeys) }, + postUpdateDeleteInput: { + keys: withDeletes.flatMap(({ postUpdateDeleteKeys }) => postUpdateDeleteKeys), + }, migrateInput: { metas: withMigrations.flatMap(({ migrateMetas }) => migrateMetas) }, }).response; break; diff --git a/src/features/migration/Migration.types.ts b/src/features/migration/Migration.types.ts index a638e295..7bd5123d 100644 --- a/src/features/migration/Migration.types.ts +++ b/src/features/migration/Migration.types.ts @@ -30,5 +30,6 @@ export type MetadataMigrationSettings = { migrateCategories: boolean; migrateTracking: boolean; deleteChapters: boolean; + migrateMetadata: boolean; migrateSortSettings: SortSettings; }; diff --git a/src/features/migration/components/MigrateDialog.tsx b/src/features/migration/components/MigrateDialog.tsx index 52262cac..dacae3eb 100644 --- a/src/features/migration/components/MigrateDialog.tsx +++ b/src/features/migration/components/MigrateDialog.tsx @@ -37,7 +37,7 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat const mangaId = Number(mangaIdAsString); const { - settings: { migrateChapters, migrateCategories, migrateTracking, deleteChapters }, + settings: { migrateChapters, migrateCategories, migrateTracking, deleteChapters, migrateMetadata }, } = useMetadataServerSettings(); const [isMigrationInProcess, setIsMigrationInProcess] = useState(false); @@ -62,6 +62,7 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat migrateCategories, migrateTracking, deleteChapters, + migrateMetadata, }); navigate(AppRoutes.manga.path(mangaIdToMigrateTo), { replace: true }); @@ -93,6 +94,12 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat checked={migrateTracking} onChange={(_, checked) => setMigrationFlag('migrateTracking', checked)} /> + setMigrationFlag('migrateMetadata', checked)} + /> } | null }; export type UpdateCategoryMetadataMutationVariables = Exact<{ + preUpdateDeleteInput: DeleteCategoryMetasInput; + hasPreUpdateDeletions: Scalars['Boolean']['input']; updateInput: SetCategoryMetasInput; hasUpdates: Scalars['Boolean']['input']; - deleteInput: DeleteCategoryMetasInput; - hasDeletions: Scalars['Boolean']['input']; + postUpdateDeleteInput: DeleteCategoryMetasInput; + hasPostUpdateDeletions: Scalars['Boolean']['input']; migrateInput: SetCategoryMetasInput; isMigration: Scalars['Boolean']['input']; }>; -export type UpdateCategoryMetadataMutation = { __typename?: 'Mutation', updatedMeta?: { __typename?: 'SetCategoryMetasPayload', metas: Array<{ __typename?: 'CategoryMetaType', categoryId: number, key: string, value: string }> } | null, deletedMeta?: { __typename?: 'DeleteCategoryMetasPayload', metas: Array<{ __typename?: 'CategoryMetaType', categoryId: number, key: string, value: string }> } | null, migrationMeta?: { __typename?: 'SetCategoryMetasPayload', metas: Array<{ __typename?: 'CategoryMetaType', categoryId: number, key: string, value: string }> } | null }; +export type UpdateCategoryMetadataMutation = { __typename?: 'Mutation', preUpdateDeletedMeta?: { __typename?: 'DeleteCategoryMetasPayload', metas: Array<{ __typename?: 'CategoryMetaType', categoryId: number, key: string, value: string }> } | null, updatedMeta?: { __typename?: 'SetCategoryMetasPayload', metas: Array<{ __typename?: 'CategoryMetaType', categoryId: number, key: string, value: string }> } | null, postUpdateDeletedMeta?: { __typename?: 'DeleteCategoryMetasPayload', metas: Array<{ __typename?: 'CategoryMetaType', categoryId: number, key: string, value: string }> } | null, migrationMeta?: { __typename?: 'SetCategoryMetasPayload', metas: Array<{ __typename?: 'CategoryMetaType', categoryId: number, key: string, value: string }> } | null }; export type GetCategoriesBaseQueryVariables = Exact<{ after?: InputMaybe; @@ -3656,16 +3658,18 @@ export type UpdateChaptersMutationVariables = Exact<{ export type UpdateChaptersMutation = { __typename?: 'Mutation', updateChapters?: { __typename?: 'UpdateChaptersPayload', chapters: Array<{ __typename?: 'ChapterType', id: number, isBookmarked?: boolean, isRead?: boolean, lastReadAt?: string, lastPageRead?: number, manga?: { __typename?: 'MangaType', id: number, unreadCount: number, bookmarkCount: number, lastReadChapter?: { __typename?: 'ChapterType', id: number } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number } | null, firstUnreadChapter?: { __typename?: 'ChapterType', id: number } | null } }> } | null, deleteDownloadedChapters?: { __typename?: 'DeleteDownloadedChaptersPayload', chapters: Array<{ __typename?: 'ChapterType', id: number, isDownloaded: boolean, manga: { __typename?: 'MangaType', id: number, downloadCount: number } }> } | null, trackProgress?: { __typename?: 'TrackProgressPayload', trackRecords: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string, private: boolean }> } | null }; export type UpdateChapterMetadataMutationVariables = Exact<{ + preUpdateDeleteInput: DeleteChapterMetasInput; + hasPreUpdateDeletions: Scalars['Boolean']['input']; updateInput: SetChapterMetasInput; hasUpdates: Scalars['Boolean']['input']; - deleteInput: DeleteChapterMetasInput; - hasDeletions: Scalars['Boolean']['input']; + postUpdateDeleteInput: DeleteChapterMetasInput; + hasPostUpdateDeletions: Scalars['Boolean']['input']; migrateInput: SetChapterMetasInput; isMigration: Scalars['Boolean']['input']; }>; -export type UpdateChapterMetadataMutation = { __typename?: 'Mutation', updatedMeta?: { __typename?: 'SetChapterMetasPayload', metas: Array<{ __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string }> } | null, deletedMeta?: { __typename?: 'DeleteChapterMetasPayload', metas: Array<{ __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string }> } | null, migrationMeta?: { __typename?: 'SetChapterMetasPayload', metas: Array<{ __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string }> } | null }; +export type UpdateChapterMetadataMutation = { __typename?: 'Mutation', preUpdateDeletedMeta?: { __typename?: 'DeleteChapterMetasPayload', metas: Array<{ __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string }> } | null, updatedMeta?: { __typename?: 'SetChapterMetasPayload', metas: Array<{ __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string }> } | null, postUpdateDeletedMeta?: { __typename?: 'DeleteChapterMetasPayload', metas: Array<{ __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string }> } | null, migrationMeta?: { __typename?: 'SetChapterMetasPayload', metas: Array<{ __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string }> } | null }; export type GetChaptersReaderQueryVariables = Exact<{ after?: InputMaybe; @@ -3970,16 +3974,18 @@ export type UpdateMangasCategoriesMutationVariables = Exact<{ export type UpdateMangasCategoriesMutation = { __typename?: 'Mutation', updateMangasCategories?: { __typename?: 'UpdateMangasCategoriesPayload', mangas: Array<{ __typename?: 'MangaType', id: number, categories: { __typename?: 'CategoryNodeList', totalCount: number, nodes: Array<{ __typename?: 'CategoryType', id: number, mangas: { __typename?: 'MangaNodeList', totalCount: number } }> } }> } | null }; export type UpdateMangaMetadataMutationVariables = Exact<{ + preUpdateDeleteInput: DeleteMangaMetasInput; + hasPreUpdateDeletions: Scalars['Boolean']['input']; updateInput: SetMangaMetasInput; hasUpdates: Scalars['Boolean']['input']; - deleteInput: DeleteMangaMetasInput; - hasDeletions: Scalars['Boolean']['input']; + postUpdateDeleteInput: DeleteMangaMetasInput; + hasPostUpdateDeletions: Scalars['Boolean']['input']; migrateInput: SetMangaMetasInput; isMigration: Scalars['Boolean']['input']; }>; -export type UpdateMangaMetadataMutation = { __typename?: 'Mutation', updatedMeta?: { __typename?: 'SetMangaMetasPayload', metas: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }> } | null, deletedMeta?: { __typename?: 'DeleteMangaMetasPayload', metas: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }> } | null, migrationMeta?: { __typename?: 'SetMangaMetasPayload', metas: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }> } | null }; +export type UpdateMangaMetadataMutation = { __typename?: 'Mutation', preUpdateDeletedMeta?: { __typename?: 'DeleteMangaMetasPayload', metas: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }> } | null, updatedMeta?: { __typename?: 'SetMangaMetasPayload', metas: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }> } | null, postUpdateDeletedMeta?: { __typename?: 'DeleteMangaMetasPayload', metas: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }> } | null, migrationMeta?: { __typename?: 'SetMangaMetasPayload', metas: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }> } | null }; export type GetMangaScreenQueryVariables = Exact<{ id: Scalars['Int']['input']; @@ -4014,10 +4020,11 @@ export type GetMangaToMigrateQueryVariables = Exact<{ getChapterData: Scalars['Boolean']['input']; migrateCategories: Scalars['Boolean']['input']; migrateTracking: Scalars['Boolean']['input']; + migrateMetadata: Scalars['Boolean']['input']; }>; -export type GetMangaToMigrateQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', id: number, inLibrary: boolean, title: string, chapters?: { __typename?: 'ChapterNodeList', totalCount: number, nodes: Array<{ __typename?: 'ChapterType', id: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number } }> }, categories?: { __typename?: 'CategoryNodeList', nodes: Array<{ __typename?: 'CategoryType', id: number }> }, trackRecords?: { __typename?: 'TrackRecordNodeList', nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, private: boolean }> } } }; +export type GetMangaToMigrateQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', id: number, inLibrary: boolean, title: string, chapters?: { __typename?: 'ChapterNodeList', totalCount: number, nodes: Array<{ __typename?: 'ChapterType', id: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number }, meta?: Array<{ __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string }> }> }, categories?: { __typename?: 'CategoryNodeList', nodes: Array<{ __typename?: 'CategoryType', id: number }> }, trackRecords?: { __typename?: 'TrackRecordNodeList', nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, private: boolean }> }, meta?: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }> } }; export type GetMangasBaseQueryVariables = Exact<{ after?: InputMaybe; @@ -4074,16 +4081,18 @@ export type GetLibraryMangaCountQueryVariables = Exact<{ [key: string]: never; } export type GetLibraryMangaCountQuery = { __typename?: 'Query', mangas: { __typename?: 'MangaNodeList', totalCount: number } }; export type UpdateGlobalMetadataMutationVariables = Exact<{ + preUpdateDeleteInput: DeleteGlobalMetasInput; + hasPreUpdateDeletions: Scalars['Boolean']['input']; updateInput: SetGlobalMetasInput; hasUpdates: Scalars['Boolean']['input']; - deleteInput: DeleteGlobalMetasInput; - hasDeletions: Scalars['Boolean']['input']; + postUpdateDeleteInput: DeleteGlobalMetasInput; + hasPostUpdateDeletions: Scalars['Boolean']['input']; migrateInput: SetGlobalMetasInput; isMigration: Scalars['Boolean']['input']; }>; -export type UpdateGlobalMetadataMutation = { __typename?: 'Mutation', updatedMeta?: { __typename?: 'SetGlobalMetasPayload', metas: Array<{ __typename?: 'GlobalMetaType', key: string, value: string }> } | null, deletedMeta?: { __typename?: 'DeleteGlobalMetasPayload', metas: Array<{ __typename?: 'GlobalMetaType', key: string, value: string }> } | null, migrationMeta?: { __typename?: 'SetGlobalMetasPayload', metas: Array<{ __typename?: 'GlobalMetaType', key: string, value: string }> } | null }; +export type UpdateGlobalMetadataMutation = { __typename?: 'Mutation', preUpdateDeletedMeta?: { __typename?: 'DeleteGlobalMetasPayload', metas: Array<{ __typename?: 'GlobalMetaType', key: string, value: string }> } | null, updatedMeta?: { __typename?: 'SetGlobalMetasPayload', metas: Array<{ __typename?: 'GlobalMetaType', key: string, value: string }> } | null, postUpdateDeletedMeta?: { __typename?: 'DeleteGlobalMetasPayload', metas: Array<{ __typename?: 'GlobalMetaType', key: string, value: string }> } | null, migrationMeta?: { __typename?: 'SetGlobalMetasPayload', metas: Array<{ __typename?: 'GlobalMetaType', key: string, value: string }> } | null }; export type GetGlobalMetadataQueryVariables = Exact<{ key: Scalars['String']['input']; @@ -4199,16 +4208,18 @@ export type UpdateSourcePreferencesMutationVariables = Exact<{ export type UpdateSourcePreferencesMutation = { __typename?: 'Mutation', updateSourcePreference?: { __typename?: 'UpdateSourcePreferencePayload', source: { __typename?: 'SourceType', id: string, name: string, displayName: string, lang: string, preferences: Array<{ __typename?: 'CheckBoxPreference', summary?: string | null, key?: string | null, type: 'CheckBoxPreference', CheckBoxCheckBoxCurrentValue?: boolean | null, CheckBoxDefault: boolean, CheckBoxTitle?: string | null } | { __typename?: 'EditTextPreference', text?: string | null, summary?: string | null, key?: string | null, dialogTitle?: string | null, dialogMessage?: string | null, type: 'EditTextPreference', EditTextPreferenceCurrentValue?: string | null, EditTextPreferenceDefault?: string | null, EditTextPreferenceTitle?: string | null } | { __typename?: 'ListPreference', summary?: string | null, key?: string | null, entryValues: Array, entries: Array, type: 'ListPreference', ListPreferenceCurrentValue?: string | null, ListPreferenceDefault?: string | null, ListPreferenceTitle?: string | null } | { __typename?: 'MultiSelectListPreference', dialogMessage?: string | null, dialogTitle?: string | null, summary?: string | null, key?: string | null, entryValues: Array, entries: Array, type: 'MultiSelectListPreference', MultiSelectListPreferenceTitle?: string | null, MultiSelectListPreferenceDefault?: Array | null, MultiSelectListPreferenceCurrentValue?: Array | null } | { __typename?: 'SwitchPreference', summary?: string | null, key?: string | null, type: 'SwitchPreference', SwitchPreferenceCurrentValue?: boolean | null, SwitchPreferenceDefault: boolean, SwitchPreferenceTitle?: string | null }> } } | null }; export type UpdateSourceMetadataMutationVariables = Exact<{ + preUpdateDeleteInput: DeleteSourceMetasInput; + hasPreUpdateDeletions: Scalars['Boolean']['input']; updateInput: SetSourceMetasInput; hasUpdates: Scalars['Boolean']['input']; - deleteInput: DeleteSourceMetasInput; - hasDeletions: Scalars['Boolean']['input']; + postUpdateDeleteInput: DeleteSourceMetasInput; + hasPostUpdateDeletions: Scalars['Boolean']['input']; migrateInput: SetSourceMetasInput; isMigration: Scalars['Boolean']['input']; }>; -export type UpdateSourceMetadataMutation = { __typename?: 'Mutation', updatedMeta?: { __typename?: 'SetSourceMetasPayload', metas: Array<{ __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }> } | null, deletedMeta?: { __typename?: 'DeleteSourceMetasPayload', metas: Array<{ __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }> } | null, migrationMeta?: { __typename?: 'SetSourceMetasPayload', metas: Array<{ __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }> } | null }; +export type UpdateSourceMetadataMutation = { __typename?: 'Mutation', preUpdateDeletedMeta?: { __typename?: 'DeleteSourceMetasPayload', metas: Array<{ __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }> } | null, updatedMeta?: { __typename?: 'SetSourceMetasPayload', metas: Array<{ __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }> } | null, postUpdateDeletedMeta?: { __typename?: 'DeleteSourceMetasPayload', metas: Array<{ __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }> } | null, migrationMeta?: { __typename?: 'SetSourceMetasPayload', metas: Array<{ __typename?: 'SourceMetaType', sourceId: string, key: string, value: string }> } | null }; export type GetSourceBrowseQueryVariables = Exact<{ id: Scalars['LongString']['input']; diff --git a/src/lib/graphql/manga/MangaMutation.ts b/src/lib/graphql/manga/MangaMutation.ts index cd408030..396d3903 100644 --- a/src/lib/graphql/manga/MangaMutation.ts +++ b/src/lib/graphql/manga/MangaMutation.ts @@ -175,19 +175,26 @@ export const UPDATE_MANGA_METADATA = gql` ${MANGA_META_FIELDS} mutation UPDATE_MANGA_METADATA( + $preUpdateDeleteInput: DeleteMangaMetasInput! + $hasPreUpdateDeletions: Boolean! $updateInput: SetMangaMetasInput! $hasUpdates: Boolean! - $deleteInput: DeleteMangaMetasInput! - $hasDeletions: Boolean! + $postUpdateDeleteInput: DeleteMangaMetasInput! + $hasPostUpdateDeletions: Boolean! $migrateInput: SetMangaMetasInput! $isMigration: Boolean! ) { + preUpdateDeletedMeta: deleteMangaMetas(input: $preUpdateDeleteInput) @include(if: $hasPreUpdateDeletions) { + metas { + ...MANGA_META_FIELDS + } + } updatedMeta: setMangaMetas(input: $updateInput) @include(if: $hasUpdates) { metas { ...MANGA_META_FIELDS } } - deletedMeta: deleteMangaMetas(input: $deleteInput) @include(if: $hasDeletions) { + postUpdateDeletedMeta: deleteMangaMetas(input: $postUpdateDeleteInput) @include(if: $hasPostUpdateDeletions) { metas { ...MANGA_META_FIELDS } diff --git a/src/lib/graphql/manga/MangaQuery.ts b/src/lib/graphql/manga/MangaQuery.ts index cf740366..d17bfe9e 100644 --- a/src/lib/graphql/manga/MangaQuery.ts +++ b/src/lib/graphql/manga/MangaQuery.ts @@ -12,9 +12,11 @@ import { MANGA_BASE_FIELDS, MANGA_LIBRARY_DUPLICATE_SCREEN_FIELDS, MANGA_LIBRARY_FIELDS, + MANGA_META_FIELDS, MANGA_READER_FIELDS, MANGA_SCREEN_FIELDS, } from '@/lib/graphql/manga/MangaFragments.ts'; +import { CHAPTER_META_FIELDS } from '@/lib/graphql/chapter/ChapterFragments.ts'; import { TRACK_RECORD_BIND_FIELDS } from '@/lib/graphql/tracker/TrackRecordFragments.ts'; // returns the current manga from the database @@ -75,11 +77,15 @@ export const GET_MANGA_CATEGORIES = gql` // returns the current manga from the database export const GET_MANGA_TO_MIGRATE = gql` + ${MANGA_META_FIELDS} + ${CHAPTER_META_FIELDS} + query GET_MANGA_TO_MIGRATE( $id: Int! $getChapterData: Boolean! $migrateCategories: Boolean! $migrateTracking: Boolean! + $migrateMetadata: Boolean! ) { manga(id: $id) { id @@ -95,6 +101,9 @@ export const GET_MANGA_TO_MIGRATE = gql` isRead isDownloaded isBookmarked + meta @include(if: $migrateMetadata) { + ...CHAPTER_META_FIELDS + } } totalCount } @@ -111,6 +120,9 @@ export const GET_MANGA_TO_MIGRATE = gql` private } } + meta @include(if: $migrateMetadata) { + ...MANGA_META_FIELDS + } } } `; diff --git a/src/lib/graphql/metadata/GlobalMetadataMutation.ts b/src/lib/graphql/metadata/GlobalMetadataMutation.ts index 127783ea..83161cf5 100644 --- a/src/lib/graphql/metadata/GlobalMetadataMutation.ts +++ b/src/lib/graphql/metadata/GlobalMetadataMutation.ts @@ -12,19 +12,26 @@ import { GLOBAL_METADATA } from '@/lib/graphql/common/Fragments.ts'; export const UPDATE_GLOBAL_METADATA = gql` ${GLOBAL_METADATA} mutation UPDATE_GLOBAL_METADATA( + $preUpdateDeleteInput: DeleteGlobalMetasInput! + $hasPreUpdateDeletions: Boolean! $updateInput: SetGlobalMetasInput! $hasUpdates: Boolean! - $deleteInput: DeleteGlobalMetasInput! - $hasDeletions: Boolean! + $postUpdateDeleteInput: DeleteGlobalMetasInput! + $hasPostUpdateDeletions: Boolean! $migrateInput: SetGlobalMetasInput! $isMigration: Boolean! ) { + preUpdateDeletedMeta: deleteGlobalMetas(input: $preUpdateDeleteInput) @include(if: $hasPreUpdateDeletions) { + metas { + ...GLOBAL_METADATA + } + } updatedMeta: setGlobalMetas(input: $updateInput) @include(if: $hasUpdates) { metas { ...GLOBAL_METADATA } } - deletedMeta: deleteGlobalMetas(input: $deleteInput) @include(if: $hasDeletions) { + postUpdateDeletedMeta: deleteGlobalMetas(input: $postUpdateDeleteInput) @include(if: $hasPostUpdateDeletions) { metas { ...GLOBAL_METADATA } diff --git a/src/lib/graphql/source/SourceMutation.ts b/src/lib/graphql/source/SourceMutation.ts index 09b03838..43fa1a0e 100644 --- a/src/lib/graphql/source/SourceMutation.ts +++ b/src/lib/graphql/source/SourceMutation.ts @@ -39,19 +39,26 @@ export const UPDATE_SOURCE_METADATA = gql` ${SOURCE_META_FIELDS} mutation UPDATE_SOURCE_METADATA( + $preUpdateDeleteInput: DeleteSourceMetasInput! + $hasPreUpdateDeletions: Boolean! $updateInput: SetSourceMetasInput! $hasUpdates: Boolean! - $deleteInput: DeleteSourceMetasInput! - $hasDeletions: Boolean! + $postUpdateDeleteInput: DeleteSourceMetasInput! + $hasPostUpdateDeletions: Boolean! $migrateInput: SetSourceMetasInput! $isMigration: Boolean! ) { + preUpdateDeletedMeta: deleteSourceMetas(input: $preUpdateDeleteInput) @include(if: $hasPreUpdateDeletions) { + metas { + ...SOURCE_META_FIELDS + } + } updatedMeta: setSourceMetas(input: $updateInput) @include(if: $hasUpdates) { metas { ...SOURCE_META_FIELDS } } - deletedMeta: deleteSourceMetas(input: $deleteInput) @include(if: $hasDeletions) { + postUpdateDeletedMeta: deleteSourceMetas(input: $postUpdateDeleteInput) @include(if: $hasPostUpdateDeletions) { metas { ...SOURCE_META_FIELDS } diff --git a/src/lib/requests/RequestManager.ts b/src/lib/requests/RequestManager.ts index a7016e2d..e139cdc9 100644 --- a/src/lib/requests/RequestManager.ts +++ b/src/lib/requests/RequestManager.ts @@ -1287,12 +1287,14 @@ export class RequestManager { public updateGlobalMeta( { + preUpdateDeleteInput = { keys: [] }, updateInput = { metas: [] }, - deleteInput = { keys: [] }, + postUpdateDeleteInput = { keys: [] }, migrateInput = { metas: [] }, }: { + preUpdateDeleteInput?: DeleteGlobalMetasInput; updateInput?: SetGlobalMetasInput; - deleteInput?: DeleteGlobalMetasInput; + postUpdateDeleteInput?: DeleteGlobalMetasInput; migrateInput?: SetGlobalMetasInput; }, options?: MutationOptions, @@ -1301,16 +1303,27 @@ export class RequestManager { GQLMethod.MUTATION, UPDATE_GLOBAL_METADATA, { + preUpdateDeleteInput, + hasPreUpdateDeletions: !!preUpdateDeleteInput.keys?.length || !!preUpdateDeleteInput.prefixes?.length, updateInput, hasUpdates: !!updateInput.metas.length, - deleteInput, - hasDeletions: !!deleteInput.keys?.length || !!deleteInput.prefixes?.length, + postUpdateDeleteInput, + hasPostUpdateDeletions: + !!postUpdateDeleteInput.keys?.length || !!postUpdateDeleteInput.prefixes?.length, migrateInput, isMigration: !!updateInput.metas.length, }, { optimisticResponse: { __typename: 'Mutation', + preUpdateDeletedMeta: { + __typename: 'DeleteGlobalMetasPayload', + metas: (preUpdateDeleteInput?.keys ?? []).map((key) => ({ + __typename: 'GlobalMetaType' as const, + key, + value: '', + })), + }, updatedMeta: { __typename: 'SetGlobalMetasPayload', metas: (updateInput?.metas ?? []).map((meta) => ({ @@ -1319,9 +1332,9 @@ export class RequestManager { value: meta.value, })), }, - deletedMeta: { + postUpdateDeletedMeta: { __typename: 'DeleteGlobalMetasPayload', - metas: (deleteInput?.keys ?? []).map((key) => ({ + metas: (postUpdateDeleteInput?.keys ?? []).map((key) => ({ __typename: 'GlobalMetaType' as const, key, value: '', @@ -1337,7 +1350,11 @@ export class RequestManager { }, }, update(cache, { data }) { - deleteInput?.keys?.forEach((key) => { + preUpdateDeleteInput?.keys?.forEach((key) => { + cache.evict({ id: cache.identify({ __typename: 'GlobalMetaType', key }) }); + }); + + postUpdateDeleteInput?.keys?.forEach((key) => { cache.evict({ id: cache.identify({ __typename: 'GlobalMetaType', key }) }); }); @@ -1712,12 +1729,14 @@ export class RequestManager { public updateSourceMeta( { + preUpdateDeleteInput = { items: [] }, updateInput = { items: [] }, - deleteInput = { items: [] }, + postUpdateDeleteInput = { items: [] }, migrateInput = { items: [] }, }: { + preUpdateDeleteInput?: DeleteSourceMetasInput; updateInput?: SetSourceMetasInput; - deleteInput?: DeleteSourceMetasInput; + postUpdateDeleteInput?: DeleteSourceMetasInput; migrateInput?: SetSourceMetasInput; }, options?: MutationOptions, @@ -1726,16 +1745,35 @@ export class RequestManager { GQLMethod.MUTATION, UPDATE_SOURCE_METADATA, { + preUpdateDeleteInput, + hasPreUpdateDeletions: preUpdateDeleteInput.items.some( + (item) => !!item.keys?.length || !!item.prefixes?.length, + ), updateInput, hasUpdates: updateInput.items.some((item) => !!item.metas?.length), - deleteInput, - hasDeletions: deleteInput.items.some((item) => !!item.keys?.length || !!item.prefixes?.length), + postUpdateDeleteInput, + hasPostUpdateDeletions: postUpdateDeleteInput.items.some( + (item) => !!item.keys?.length || !!item.prefixes?.length, + ), migrateInput, isMigration: migrateInput.items.some((item) => !!item.metas?.length), }, { optimisticResponse: { __typename: 'Mutation', + preUpdateDeletedMeta: { + __typename: 'DeleteSourceMetasPayload', + metas: (preUpdateDeleteInput?.items ?? []).flatMap((item) => + item.sourceIds.flatMap((sourceId) => + (item.keys ?? []).map((key) => ({ + __typename: 'SourceMetaType' as const, + sourceId, + key, + value: '', + })), + ), + ), + }, updatedMeta: { __typename: 'SetSourceMetasPayload', metas: (updateInput?.items ?? []).flatMap((item) => @@ -1749,9 +1787,9 @@ export class RequestManager { ), ), }, - deletedMeta: { + postUpdateDeletedMeta: { __typename: 'DeleteSourceMetasPayload', - metas: (deleteInput?.items ?? []).flatMap((item) => + metas: (postUpdateDeleteInput?.items ?? []).flatMap((item) => item.sourceIds.flatMap((sourceId) => (item.keys ?? []).map((key) => ({ __typename: 'SourceMetaType' as const, @@ -1777,7 +1815,17 @@ export class RequestManager { }, }, update(cache, { data }) { - deleteInput?.items.forEach((item) => + preUpdateDeleteInput?.items.forEach((item) => + item.sourceIds.forEach((sourceId) => + item.keys?.forEach((key) => { + cache.evict({ + id: cache.identify({ __typename: 'SourceMetaType', sourceId, key }), + }); + }), + ), + ); + + postUpdateDeleteInput?.items.forEach((item) => item.sourceIds.forEach((sourceId) => item.keys?.forEach((key) => { cache.evict({ @@ -2103,6 +2151,7 @@ export class RequestManager { migrateCategories = false, migrateTracking = false, deleteChapters = false, + migrateMetadata = false, apolloOptions: options, }: Partial & { apolloOptions?: QueryOptions; @@ -2116,6 +2165,7 @@ export class RequestManager { getChapterData: migrateChapters || deleteChapters, migrateCategories, migrateTracking, + migrateMetadata, }, options, ); @@ -2275,12 +2325,14 @@ export class RequestManager { public updateMangaMeta( { + preUpdateDeleteInput = { items: [] }, updateInput = { items: [] }, - deleteInput = { items: [] }, + postUpdateDeleteInput = { items: [] }, migrateInput = { items: [] }, }: { + preUpdateDeleteInput?: DeleteMangaMetasInput; updateInput?: SetMangaMetasInput; - deleteInput?: DeleteMangaMetasInput; + postUpdateDeleteInput?: DeleteMangaMetasInput; migrateInput?: SetMangaMetasInput; }, options?: MutationOptions, @@ -2289,16 +2341,35 @@ export class RequestManager { GQLMethod.MUTATION, UPDATE_MANGA_METADATA, { + preUpdateDeleteInput, + hasPreUpdateDeletions: preUpdateDeleteInput.items.some( + (item) => !!item.keys?.length || !!item.prefixes?.length, + ), updateInput, hasUpdates: updateInput.items.some((item) => !!item.metas?.length), - deleteInput, - hasDeletions: deleteInput.items.some((item) => !!item.keys?.length || !!item.prefixes?.length), + postUpdateDeleteInput, + hasPostUpdateDeletions: postUpdateDeleteInput.items.some( + (item) => !!item.keys?.length || !!item.prefixes?.length, + ), migrateInput, isMigration: migrateInput.items.some((item) => !!item.metas?.length), }, { optimisticResponse: { __typename: 'Mutation', + preUpdateDeletedMeta: { + __typename: 'DeleteMangaMetasPayload', + metas: (preUpdateDeleteInput?.items ?? []).flatMap((item) => + item.mangaIds.flatMap((mangaId) => + (item.keys ?? []).map((key) => ({ + __typename: 'MangaMetaType' as const, + mangaId, + key, + value: '', + })), + ), + ), + }, updatedMeta: { __typename: 'SetMangaMetasPayload', metas: (updateInput?.items ?? []).flatMap((item) => @@ -2312,9 +2383,9 @@ export class RequestManager { ), ), }, - deletedMeta: { + postUpdateDeletedMeta: { __typename: 'DeleteMangaMetasPayload', - metas: (deleteInput?.items ?? []).flatMap((item) => + metas: (postUpdateDeleteInput?.items ?? []).flatMap((item) => item.mangaIds.flatMap((mangaId) => (item.keys ?? []).map((key) => ({ __typename: 'MangaMetaType' as const, @@ -2340,7 +2411,17 @@ export class RequestManager { }, }, update(cache, { data }) { - deleteInput?.items.forEach((item) => + preUpdateDeleteInput?.items.forEach((item) => + item.mangaIds.forEach((mangaId) => + item.keys?.forEach((key) => { + cache.evict({ + id: cache.identify({ __typename: 'MangaMetaType', mangaId, key }), + }); + }), + ), + ); + + postUpdateDeleteInput?.items.forEach((item) => item.mangaIds.forEach((mangaId) => item.keys?.forEach((key) => { cache.evict({ @@ -2543,12 +2624,14 @@ export class RequestManager { public updateChapterMeta( { + preUpdateDeleteInput = { items: [] }, updateInput = { items: [] }, - deleteInput = { items: [] }, + postUpdateDeleteInput = { items: [] }, migrateInput = { items: [] }, }: { + preUpdateDeleteInput?: DeleteChapterMetasInput; updateInput?: SetChapterMetasInput; - deleteInput?: DeleteChapterMetasInput; + postUpdateDeleteInput?: DeleteChapterMetasInput; migrateInput?: SetChapterMetasInput; }, options?: MutationOptions, @@ -2557,16 +2640,35 @@ export class RequestManager { GQLMethod.MUTATION, UPDATE_CHAPTER_METADATA, { + preUpdateDeleteInput, + hasPreUpdateDeletions: preUpdateDeleteInput.items.some( + (item) => !!item.keys?.length || !!item.prefixes?.length, + ), updateInput, hasUpdates: updateInput.items.some((item) => !!item.metas?.length), - deleteInput, - hasDeletions: deleteInput.items.some((item) => !!item.keys?.length || !!item.prefixes?.length), + postUpdateDeleteInput, + hasPostUpdateDeletions: postUpdateDeleteInput.items.some( + (item) => !!item.keys?.length || !!item.prefixes?.length, + ), migrateInput, isMigration: migrateInput.items.some((item) => !!item.metas?.length), }, { optimisticResponse: { __typename: 'Mutation', + preUpdateDeletedMeta: { + __typename: 'DeleteChapterMetasPayload', + metas: (preUpdateDeleteInput?.items ?? []).flatMap((item) => + item.chapterIds.flatMap((chapterId) => + (item.keys ?? []).map((key) => ({ + __typename: 'ChapterMetaType' as const, + chapterId, + key, + value: '', + })), + ), + ), + }, updatedMeta: { __typename: 'SetChapterMetasPayload', metas: (updateInput?.items ?? []).flatMap((item) => @@ -2580,9 +2682,9 @@ export class RequestManager { ), ), }, - deletedMeta: { + postUpdateDeletedMeta: { __typename: 'DeleteChapterMetasPayload', - metas: (deleteInput?.items ?? []).flatMap((item) => + metas: (postUpdateDeleteInput?.items ?? []).flatMap((item) => item.chapterIds.flatMap((chapterId) => (item.keys ?? []).map((key) => ({ __typename: 'ChapterMetaType' as const, @@ -2608,7 +2710,17 @@ export class RequestManager { }, }, update(cache, { data }) { - deleteInput?.items.forEach((item) => + preUpdateDeleteInput?.items.forEach((item) => + item.chapterIds.forEach((chapterId) => + item.keys?.forEach((key) => { + cache.evict({ + id: cache.identify({ __typename: 'ChapterMetaType', chapterId, key }), + }); + }), + ), + ); + + postUpdateDeleteInput?.items.forEach((item) => item.chapterIds.forEach((chapterId) => item.keys?.forEach((key) => { cache.evict({ @@ -2863,12 +2975,14 @@ export class RequestManager { public updateCategoryMeta( { + preUpdateDeleteInput = { items: [] }, updateInput = { items: [] }, - deleteInput = { items: [] }, + postUpdateDeleteInput = { items: [] }, migrateInput = { items: [] }, }: { + preUpdateDeleteInput?: DeleteCategoryMetasInput; updateInput?: SetCategoryMetasInput; - deleteInput?: DeleteCategoryMetasInput; + postUpdateDeleteInput?: DeleteCategoryMetasInput; migrateInput?: SetCategoryMetasInput; }, options?: MutationOptions, @@ -2877,16 +2991,35 @@ export class RequestManager { GQLMethod.MUTATION, UPDATE_CATEGORY_METADATA, { + preUpdateDeleteInput, + hasPreUpdateDeletions: preUpdateDeleteInput.items.some( + (item) => !!item.keys?.length || !!item.prefixes?.length, + ), updateInput, hasUpdates: updateInput.items.some((item) => !!item.metas?.length), - deleteInput, - hasDeletions: deleteInput.items.some((item) => !!item.keys?.length || !!item.prefixes?.length), + postUpdateDeleteInput, + hasPostUpdateDeletions: postUpdateDeleteInput.items.some( + (item) => !!item.keys?.length || !!item.prefixes?.length, + ), migrateInput, isMigration: migrateInput.items.some((item) => !!item.metas?.length), }, { optimisticResponse: { __typename: 'Mutation', + preUpdateDeletedMeta: { + __typename: 'DeleteCategoryMetasPayload', + metas: (preUpdateDeleteInput?.items ?? []).flatMap((item) => + item.categoryIds.flatMap((categoryId) => + (item.keys ?? []).map((key) => ({ + __typename: 'CategoryMetaType' as const, + categoryId, + key, + value: '', + })), + ), + ), + }, updatedMeta: { __typename: 'SetCategoryMetasPayload', metas: (updateInput?.items ?? []).flatMap((item) => @@ -2900,9 +3033,9 @@ export class RequestManager { ), ), }, - deletedMeta: { + postUpdateDeletedMeta: { __typename: 'DeleteCategoryMetasPayload', - metas: (deleteInput?.items ?? []).flatMap((item) => + metas: (postUpdateDeleteInput?.items ?? []).flatMap((item) => item.categoryIds.flatMap((categoryId) => (item.keys ?? []).map((key) => ({ __typename: 'CategoryMetaType' as const, @@ -2928,7 +3061,17 @@ export class RequestManager { }, }, update(cache, { data }) { - deleteInput?.items.forEach((item) => + preUpdateDeleteInput?.items.forEach((item) => + item.categoryIds.forEach((categoryId) => + item.keys?.forEach((key) => { + cache.evict({ + id: cache.identify({ __typename: 'CategoryMetaType', categoryId, key }), + }); + }), + ), + ); + + postUpdateDeleteInput?.items.forEach((item) => item.categoryIds.forEach((categoryId) => item.keys?.forEach((key) => { cache.evict({