📔 JsDoc for index.ts

This commit is contained in:
2026-03-05 11:00:56 -05:00
parent 74c0d6513c
commit 5d18bd1e43
3 changed files with 53 additions and 123 deletions

View File

@@ -307,7 +307,7 @@ export const Import = (props: IProps): ReactElement => {
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
{format(
new Date(jobResult.earliestTimestamp),
"EEEE, hh:mma, do LLLL Y",
"EEEE, hh:mma, do LLLL y",
)}
</td>
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">

View File

@@ -278,23 +278,9 @@ export type InferredMetadataInput = {
export type Issue = {
__typename?: 'Issue';
api_detail_url?: Maybe<Scalars['String']['output']>;
character_credits?: Maybe<Array<CharacterCredit>>;
cover_date?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
id: Scalars['Int']['output'];
image?: Maybe<ImageUrls>;
issue_number?: Maybe<Scalars['String']['output']>;
location_credits?: Maybe<Array<LocationCredit>>;
name?: Maybe<Scalars['String']['output']>;
number?: Maybe<Scalars['Int']['output']>;
person_credits?: Maybe<Array<PersonCredit>>;
site_detail_url?: Maybe<Scalars['String']['output']>;
store_date?: Maybe<Scalars['String']['output']>;
story_arc_credits?: Maybe<Array<StoryArcCredit>>;
subtitle?: Maybe<Scalars['String']['output']>;
team_credits?: Maybe<Array<TeamCredit>>;
volume?: Maybe<Volume>;
year?: Maybe<Scalars['String']['output']>;
};
@@ -534,9 +520,10 @@ export type PullListItem = {
__typename?: 'PullListItem';
cover?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
name: Scalars['String']['output'];
name?: Maybe<Scalars['String']['output']>;
potw?: Maybe<Scalars['Int']['output']>;
price?: Maybe<Scalars['String']['output']>;
publicationDate?: Maybe<Scalars['String']['output']>;
publisher?: Maybe<Scalars['String']['output']>;
pulls?: Maybe<Scalars['Int']['output']>;
rating?: Maybe<Scalars['Float']['output']>;
@@ -1009,7 +996,7 @@ export type GetWeeklyPullListQueryVariables = Exact<{
}>;
export type GetWeeklyPullListQuery = { __typename?: 'Query', getWeeklyPullList: { __typename?: 'PullListResponse', result: Array<{ __typename?: 'PullListItem', name: string, publisher?: string | null, cover?: string | null }> } };
export type GetWeeklyPullListQuery = { __typename?: 'Query', getWeeklyPullList: { __typename?: 'PullListResponse', result: Array<{ __typename?: 'PullListItem', name?: string | null, publisher?: string | null, cover?: string | null }> } };
export type GetLibraryComicsQueryVariables = Exact<{
page?: InputMaybe<Scalars['Int']['input']>;

View File

@@ -1,177 +1,120 @@
import { create } from "zustand";
import io, { Socket } from "socket.io-client";
import { SOCKET_BASE_URI } from "../constants/endpoints";
import { isNil } from "lodash";
import { QueryClient } from "@tanstack/react-query";
import { toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
const queryClient = new QueryClient();
// Type for global state
/**
* Global application state interface
*/
interface StoreState {
/** Active socket.io connections by namespace */
socketInstances: Record<string, Socket>;
/**
* Get or create socket connection for namespace
* @param namespace - Socket namespace (default: "/")
* @returns Socket instance
*/
getSocket: (namespace?: string) => Socket;
/**
* Disconnect and remove socket instance
* @param namespace - Socket namespace to disconnect
*/
disconnectSocket: (namespace: string) => void;
/** ComicVine scraping state */
comicvine: {
scrapingStatus: string;
};
/** Import job queue state and actions */
importJobQueue: {
successfulJobCount: number;
failedJobCount: number;
status: string | undefined;
mostRecentImport: string | null;
setStatus: (status: string) => void;
setJobCount: (jobType: string, count: number) => void;
setMostRecentImport: (fileName: string) => void;
};
}
/**
* Zustand store for global app state and socket management
*/
export const useStore = create<StoreState>((set, get) => ({
socketInstances: {},
getSocket: (namespace = "/") => {
const fullNamespace = namespace === "/" ? "" : namespace;
const ns = namespace === "/" ? "" : namespace;
const existing = get().socketInstances[namespace];
if (existing && existing.connected) return existing;
if (existing?.connected) return existing;
const sessionId = localStorage.getItem("sessionId");
const socket = io(`${SOCKET_BASE_URI}${fullNamespace}`, {
const socket = io(`${SOCKET_BASE_URI}${ns}`, {
transports: ["websocket"],
withCredentials: true,
query: { sessionId },
});
socket.on("connect", () => {
// Socket connected successfully
});
socket.on("sessionInitialized", (id) => localStorage.setItem("sessionId", id));
if (sessionId) socket.emit("call", "socket.resumeSession", { sessionId, namespace });
// Always listen for sessionInitialized in case backend creates a new session
socket.on("sessionInitialized", (id) => {
localStorage.setItem("sessionId", id);
});
socket.on("RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION", ({ completedJobCount, failedJobCount, queueStatus }) =>
set((s) => ({ importJobQueue: { ...s.importJobQueue, successfulJobCount: completedJobCount, failedJobCount, status: queueStatus } }))
);
if (sessionId) {
socket.emit("call", "socket.resumeSession", { sessionId, namespace });
}
socket.on("LS_COVER_EXTRACTED", ({ completedJobCount, importResult }) =>
set((s) => ({ importJobQueue: { ...s.importJobQueue, successfulJobCount: completedJobCount, mostRecentImport: importResult.data.rawFileDetails.name } }))
);
socket.on("RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION", (data) => {
const { completedJobCount, failedJobCount, queueStatus } = data;
set((state) => ({
importJobQueue: {
...state.importJobQueue,
successfulJobCount: completedJobCount,
failedJobCount,
status: queueStatus,
},
}));
});
socket.on("LS_COVER_EXTRACTED", ({ completedJobCount, importResult }) => {
set((state) => ({
importJobQueue: {
...state.importJobQueue,
successfulJobCount: completedJobCount,
mostRecentImport: importResult.data.rawFileDetails.name,
},
}));
});
socket.on("LS_COVER_EXTRACTION_FAILED", ({ failedJobCount }) => {
set((state) => ({
importJobQueue: {
...state.importJobQueue,
failedJobCount,
},
}));
});
socket.on("LS_COVER_EXTRACTION_FAILED", ({ failedJobCount }) =>
set((s) => ({ importJobQueue: { ...s.importJobQueue, failedJobCount } }))
);
socket.on("LS_IMPORT_QUEUE_DRAINED", () => {
set((state) => ({
importJobQueue: {
...state.importJobQueue,
status: "drained",
},
}));
// Delay query invalidation and sessionId removal to ensure backend has persisted data
set((s) => ({ importJobQueue: { ...s.importJobQueue, status: "drained" } }));
setTimeout(() => {
queryClient.invalidateQueries({ queryKey: ["allImportJobResults"] });
localStorage.removeItem("sessionId");
}, 500);
});
socket.on("CV_SCRAPING_STATUS", (data) => {
set((state) => ({
comicvine: {
...state.comicvine,
scrapingStatus: data.message,
},
}));
});
socket.on("CV_SCRAPING_STATUS", ({ message }) =>
set((s) => ({ comicvine: { ...s.comicvine, scrapingStatus: message } }))
);
socket.on("searchResultsAvailable", (data) => {
toast(`Results found for query: ${JSON.stringify(data.query, null, 2)}`);
});
set((state) => ({
socketInstances: {
...state.socketInstances,
[namespace]: socket,
},
}));
socket.on("searchResultsAvailable", ({ query }) =>
toast(`Results found for query: ${JSON.stringify(query, null, 2)}`)
);
set((s) => ({ socketInstances: { ...s.socketInstances, [namespace]: socket } }));
return socket;
},
disconnectSocket: (namespace: string) => {
disconnectSocket: (namespace) => {
const socket = get().socketInstances[namespace];
if (socket) {
socket.disconnect();
set((state) => {
const { [namespace]: _, ...rest } = state.socketInstances;
set((s) => {
const { [namespace]: _, ...rest } = s.socketInstances;
return { socketInstances: rest };
});
}
},
comicvine: {
scrapingStatus: "",
},
comicvine: { scrapingStatus: "" },
importJobQueue: {
successfulJobCount: 0,
failedJobCount: 0,
status: undefined,
mostRecentImport: null,
setStatus: (status: string) =>
set((state) => ({
importJobQueue: {
...state.importJobQueue,
status,
},
})),
setJobCount: (jobType: string, count: number) =>
set((state) => ({
importJobQueue: {
...state.importJobQueue,
...(jobType === "successful"
? { successfulJobCount: count }
: { failedJobCount: count }),
},
})),
setMostRecentImport: (fileName: string) =>
set((state) => ({
importJobQueue: {
...state.importJobQueue,
mostRecentImport: fileName,
},
})),
setStatus: (status) => set((s) => ({ importJobQueue: { ...s.importJobQueue, status } })),
setJobCount: (jobType, count) => set((s) => ({
importJobQueue: { ...s.importJobQueue, [jobType === "successful" ? "successfulJobCount" : "failedJobCount"]: count }
})),
setMostRecentImport: (fileName) => set((s) => ({ importJobQueue: { ...s.importJobQueue, mostRecentImport: fileName } })),
},
}));