Преглед изворни кода

fix(studio): wait for async book creation before failing

Ma пре 7 месеци
родитељ
комит
1a6b50b807
2 измењених фајлова са 34 додато и 4 уклоњено
  1. 14 2
      packages/studio/src/pages/BookCreate.tsx
  2. 20 2
      packages/studio/src/pages/page-state.test.ts

+ 14 - 2
packages/studio/src/pages/BookCreate.tsx

@@ -58,19 +58,23 @@ interface WaitForBookReadyOptions {
   readonly waitImpl?: (ms: number) => Promise<void>;
   readonly waitImpl?: (ms: number) => Promise<void>;
 }
 }
 
 
+const DEFAULT_BOOK_READY_MAX_ATTEMPTS = 120;
+const DEFAULT_BOOK_READY_DELAY_MS = 250;
+
 export async function waitForBookReady(
 export async function waitForBookReady(
   bookId: string,
   bookId: string,
   options: WaitForBookReadyOptions = {},
   options: WaitForBookReadyOptions = {},
 ): Promise<void> {
 ): Promise<void> {
   const fetchBook = options.fetchBook ?? ((id: string) => fetchJson(`/books/${id}`));
   const fetchBook = options.fetchBook ?? ((id: string) => fetchJson(`/books/${id}`));
   const fetchStatus = options.fetchStatus ?? ((id: string) => fetchJson<{ status: string; error?: string }>(`/books/${id}/create-status`));
   const fetchStatus = options.fetchStatus ?? ((id: string) => fetchJson<{ status: string; error?: string }>(`/books/${id}/create-status`));
-  const maxAttempts = options.maxAttempts ?? 20;
-  const delayMs = options.delayMs ?? 150;
+  const maxAttempts = options.maxAttempts ?? DEFAULT_BOOK_READY_MAX_ATTEMPTS;
+  const delayMs = options.delayMs ?? DEFAULT_BOOK_READY_DELAY_MS;
   const waitImpl = options.waitImpl ?? ((ms: number) => new Promise<void>((resolve) => {
   const waitImpl = options.waitImpl ?? ((ms: number) => new Promise<void>((resolve) => {
     setTimeout(resolve, ms);
     setTimeout(resolve, ms);
   }));
   }));
 
 
   let lastError: unknown;
   let lastError: unknown;
+  let lastKnownStatus: string | undefined;
 
 
   for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
   for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
     try {
     try {
@@ -80,6 +84,7 @@ export async function waitForBookReady(
       lastError = error;
       lastError = error;
       try {
       try {
         const status = await fetchStatus(bookId);
         const status = await fetchStatus(bookId);
+        lastKnownStatus = status.status;
         if (status.status === "error") {
         if (status.status === "error") {
           throw new Error(status.error ?? `Book "${bookId}" failed to create`);
           throw new Error(status.error ?? `Book "${bookId}" failed to create`);
         }
         }
@@ -89,12 +94,19 @@ export async function waitForBookReady(
         }
         }
       }
       }
       if (attempt === maxAttempts - 1) {
       if (attempt === maxAttempts - 1) {
+        if (lastKnownStatus === "creating") {
+          break;
+        }
         throw error;
         throw error;
       }
       }
       await waitImpl(delayMs);
       await waitImpl(delayMs);
     }
     }
   }
   }
 
 
+  if (lastKnownStatus === "creating") {
+    throw new Error(`Book "${bookId}" is still being created. Wait a moment and refresh.`);
+  }
+
   throw lastError instanceof Error ? lastError : new Error(`Book "${bookId}" was not ready`);
   throw lastError instanceof Error ? lastError : new Error(`Book "${bookId}" was not ready`);
 }
 }
 
 

+ 20 - 2
packages/studio/src/pages/page-state.test.ts

@@ -52,7 +52,25 @@ describe("waitForBookReady", () => {
     expect(attempts).toBe(3);
     expect(attempts).toBe(3);
   });
   });
 
 
-  it("surfaces the last error when the book never becomes readable", async () => {
+  it("keeps polling while the server still reports the book as creating", async () => {
+    let attempts = 0;
+
+    await expect(waitForBookReady("slow-book", {
+      fetchBook: async () => {
+        attempts += 1;
+        if (attempts < 25) {
+          throw new Error("Book not found");
+        }
+      },
+      fetchStatus: async () => ({ status: "creating" }),
+      delayMs: 0,
+      waitImpl: async () => undefined,
+    })).resolves.toBeUndefined();
+
+    expect(attempts).toBe(25);
+  });
+
+  it("surfaces a clear timeout when the book is still being created", async () => {
     await expect(waitForBookReady("missing-book", {
     await expect(waitForBookReady("missing-book", {
       fetchBook: async () => {
       fetchBook: async () => {
         throw new Error("Book not found");
         throw new Error("Book not found");
@@ -61,7 +79,7 @@ describe("waitForBookReady", () => {
       maxAttempts: 2,
       maxAttempts: 2,
       delayMs: 0,
       delayMs: 0,
       waitImpl: async () => undefined,
       waitImpl: async () => undefined,
-    })).rejects.toThrow("Book not found");
+    })).rejects.toThrow('Book "missing-book" is still being created. Wait a moment and refresh.');
   });
   });
 
 
   it("prefers the server-reported create failure over a polling timeout", async () => {
   it("prefers the server-reported create failure over a polling timeout", async () => {