feat(offline): track structured failure details during caching

Replace the failure counter with a list of typed failures (fetch,
build, save, readme, unknown) carrying path, sha, message, and
affected doc ids so callers can surface diagnostics instead of just
a count.
This commit is contained in:
Julien Calixte
2026-05-17 00:07:13 +02:00
parent dd576ced90
commit 2994839ee5

View File

@@ -11,6 +11,26 @@ import { useUserRepoStore } from "@/modules/repo/store/userRepo.store"
const CONCURRENCY = 8 const CONCURRENCY = 8
const BULK_FLUSH_SIZE = 50 const BULK_FLUSH_SIZE = 50
export type CacheFailureKind =
| "fetch"
| "build"
| "save"
| "readme"
| "unknown"
export type CacheFailure = {
kind: CacheFailureKind
path?: string
sha?: string
message?: string
docIds?: string[]
}
const describeError = (error: unknown): string => {
if (error instanceof Error) return error.message
return String(error)
}
const runWithConcurrency = async <T>( const runWithConcurrency = async <T>(
items: T[], items: T[],
limit: number, limit: number,
@@ -33,7 +53,13 @@ export const useOfflineNotes = () => {
const totalOfNotes = computed(() => store.files.length) const totalOfNotes = computed(() => store.files.length)
const noteCompleted = ref(0) const noteCompleted = ref(0)
const failedNotes = ref(0) const failures = ref<CacheFailure[]>([])
const failedNotes = computed(() => failures.value.length)
const recordFailure = (failure: CacheFailure) => {
failures.value.push(failure)
console.warn("[offline-cache] failure", failure)
}
const cacheAllNotes = async () => { const cacheAllNotes = async () => {
const isInitialized = store.user && store.repo && totalOfNotes.value > 0 const isInitialized = store.user && store.repo && totalOfNotes.value > 0
@@ -56,13 +82,23 @@ export const useOfflineNotes = () => {
) )
noteCompleted.value = store.files.length - filesToFetch.length noteCompleted.value = store.files.length - filesToFetch.length
failedNotes.value = 0 failures.value = []
const pendingDocs: Note[] = [] const pendingDocs: Note[] = []
const flush = async () => { const flush = async () => {
if (!pendingDocs.length) return if (!pendingDocs.length) return
const batch = pendingDocs.splice(0, pendingDocs.length) const batch = pendingDocs.splice(0, pendingDocs.length)
try {
await data.bulkUpdate(batch) await data.bulkUpdate(batch)
} catch (error) {
recordFailure({
kind: "save",
message: describeError(error),
docIds: batch
.map((doc) => doc._id)
.filter((id): id is string => Boolean(id))
})
}
} }
const fetchWork = runWithConcurrency( const fetchWork = runWithConcurrency(
@@ -75,22 +111,45 @@ export const useOfflineNotes = () => {
store.repo, store.repo,
file.sha! file.sha!
) )
if (!content) { if (content === null) {
failedNotes.value++ recordFailure({
kind: "fetch",
path: file.path,
sha: file.sha!
})
return return
} }
pendingDocs.push(...buildNoteDocs(file.sha!, file.path, content)) let docs: Note[]
try {
docs = buildNoteDocs(file.sha!, file.path, content)
} catch (error) {
recordFailure({
kind: "build",
path: file.path,
sha: file.sha!,
message: describeError(error)
})
return
}
pendingDocs.push(...docs)
if (pendingDocs.length >= BULK_FLUSH_SIZE) { if (pendingDocs.length >= BULK_FLUSH_SIZE) {
await flush() await flush()
} }
} catch (error) {
recordFailure({
kind: "unknown",
path: file.path,
sha: file.sha,
message: describeError(error)
})
} finally { } finally {
noteCompleted.value++ noteCompleted.value++
} }
} }
) )
const readmeWork = getMainReadme(store.user, store.repo).catch(() => { const readmeWork = getMainReadme(store.user, store.repo).catch((error) => {
failedNotes.value++ recordFailure({ kind: "readme", message: describeError(error) })
}) })
await Promise.all([fetchWork, readmeWork]) await Promise.all([fetchWork, readmeWork])
@@ -105,6 +164,7 @@ export const useOfflineNotes = () => {
isLoading, isLoading,
totalOfNotes, totalOfNotes,
noteCompleted, noteCompleted,
failedNotes failedNotes,
failures
} }
} }