@shipfox/client-logs 27.0.1 → 29.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-type$colon$emit.log +0 -1
- package/CHANGELOG.md +10 -0
- package/dist/components/agent-session-rows.js +273 -232
- package/dist/components/agent-session-rows.js.map +1 -1
- package/dist/components/log-view.d.ts.map +1 -1
- package/dist/components/log-view.js +4 -1
- package/dist/components/log-view.js.map +1 -1
- package/dist/core/log-tree.d.ts.map +1 -1
- package/dist/core/log-tree.js +109 -123
- package/dist/core/log-tree.js.map +1 -1
- package/dist/hooks/api/step-logs.d.ts.map +1 -1
- package/dist/hooks/api/step-logs.js +51 -38
- package/dist/hooks/api/step-logs.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/components/agent-session-rows.tsx +259 -177
- package/src/components/log-view.tsx +7 -5
- package/src/core/log-tree.ts +117 -104
- package/src/hooks/api/step-logs.ts +73 -47
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -67,44 +67,16 @@ export function isMissingStepLogStreamError(error) {
|
|
|
67
67
|
const query = useQuery({
|
|
68
68
|
queryKey,
|
|
69
69
|
enabled,
|
|
70
|
-
queryFn:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
signal
|
|
81
|
-
});
|
|
82
|
-
} catch (error) {
|
|
83
|
-
if (options.retryMissingStream && previous === undefined && isMissingStepLogStreamError(error) && !manualRefetch) {
|
|
84
|
-
const retryCount = options.missingStreamRetryCount;
|
|
85
|
-
if (retryCount === undefined) throw error;
|
|
86
|
-
if (missingStreamFailureCountRef.current >= retryCount) {
|
|
87
|
-
return emptyCompleteLogSnapshot();
|
|
88
|
-
}
|
|
89
|
-
missingStreamFailureCountRef.current += 1;
|
|
90
|
-
}
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
missingStreamFailureCountRef.current = 0;
|
|
94
|
-
if (response.mode === 'presigned') {
|
|
95
|
-
const ndjson = await readPresignedLogObject(response.url, signal);
|
|
96
|
-
return mergeLogRead(previous, {
|
|
97
|
-
mode: 'presigned',
|
|
98
|
-
response,
|
|
99
|
-
records: parseLogNdjson(ndjson)
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
return mergeLogRead(previous, {
|
|
103
|
-
mode: 'inline',
|
|
104
|
-
response,
|
|
105
|
-
records: parseLogNdjson(response.ndjson)
|
|
106
|
-
});
|
|
107
|
-
},
|
|
70
|
+
queryFn: ({ signal })=>loadStepLogsQuery({
|
|
71
|
+
signal,
|
|
72
|
+
stepId,
|
|
73
|
+
attempt,
|
|
74
|
+
queryKey,
|
|
75
|
+
queryClient,
|
|
76
|
+
options,
|
|
77
|
+
manualRefetchRef,
|
|
78
|
+
missingStreamFailureCountRef
|
|
79
|
+
}),
|
|
108
80
|
retry: (failureCount, error)=>{
|
|
109
81
|
if (initialErrorRetryCount <= 0) return false;
|
|
110
82
|
if (queryClient.getQueryData(queryKey) !== undefined) return false;
|
|
@@ -141,6 +113,47 @@ export function isMissingStepLogStreamError(error) {
|
|
|
141
113
|
refetchLogs
|
|
142
114
|
};
|
|
143
115
|
}
|
|
116
|
+
async function loadStepLogsQuery(params) {
|
|
117
|
+
const previous = params.queryClient.getQueryData(params.queryKey);
|
|
118
|
+
const manualRefetch = params.manualRefetchRef.current;
|
|
119
|
+
params.manualRefetchRef.current = false;
|
|
120
|
+
let response;
|
|
121
|
+
try {
|
|
122
|
+
response = await readStepAttemptLogsPage({
|
|
123
|
+
stepId: params.stepId ?? '',
|
|
124
|
+
attempt: params.attempt ?? 0,
|
|
125
|
+
cursor: previous?.nextCursor ?? 0,
|
|
126
|
+
signal: params.signal
|
|
127
|
+
});
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return handleMissingStreamLoadError(error, previous, manualRefetch, params);
|
|
130
|
+
}
|
|
131
|
+
params.missingStreamFailureCountRef.current = 0;
|
|
132
|
+
if (response.mode === 'presigned') {
|
|
133
|
+
const ndjson = await readPresignedLogObject(response.url, params.signal);
|
|
134
|
+
return mergeLogRead(previous, {
|
|
135
|
+
mode: 'presigned',
|
|
136
|
+
response,
|
|
137
|
+
records: parseLogNdjson(ndjson)
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return mergeLogRead(previous, {
|
|
141
|
+
mode: 'inline',
|
|
142
|
+
response,
|
|
143
|
+
records: parseLogNdjson(response.ndjson)
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function handleMissingStreamLoadError(error, previous, manualRefetch, params) {
|
|
147
|
+
const retryable = params.options.retryMissingStream && previous === undefined && isMissingStepLogStreamError(error) && !manualRefetch;
|
|
148
|
+
if (!retryable) throw error;
|
|
149
|
+
const retryCount = params.options.missingStreamRetryCount;
|
|
150
|
+
if (retryCount === undefined) throw error;
|
|
151
|
+
if (params.missingStreamFailureCountRef.current >= retryCount) {
|
|
152
|
+
return emptyCompleteLogSnapshot();
|
|
153
|
+
}
|
|
154
|
+
params.missingStreamFailureCountRef.current += 1;
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
144
157
|
function emptyCompleteLogSnapshot() {
|
|
145
158
|
return {
|
|
146
159
|
records: [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/hooks/api/step-logs.ts"],"sourcesContent":["import {readLogsResponseSchema} from '@shipfox/api-logs-dto';\nimport {ApiError, checkedApiRequest} from '@shipfox/client-api';\nimport {useQuery, useQueryClient} from '@tanstack/react-query';\nimport {useCallback, useRef} from 'react';\nimport {\n mergeLogRead,\n STEP_LOG_LIVE_REFETCH_MS,\n type StepLogSnapshot,\n stepLogRefetchInterval,\n} from '#core/log-read.js';\nimport {parseLogNdjson, toLogRead} from './log-mapper.js';\n\nexport const stepLogsQueryKeys = {\n all: ['step-logs'] as const,\n detail: (stepId: string, attempt: number) =>\n [...stepLogsQueryKeys.all, 'detail', stepId, attempt] as const,\n};\n\ninterface ReadStepAttemptLogsPageParams {\n stepId: string;\n attempt: number;\n cursor: number;\n signal?: AbortSignal;\n}\n\nexport async function readStepAttemptLogsPage({\n stepId,\n attempt,\n cursor,\n signal,\n}: ReadStepAttemptLogsPageParams) {\n const params = new URLSearchParams({cursor: String(cursor)});\n const response = await checkedApiRequest(\n readLogsResponseSchema,\n `/steps/${encodeURIComponent(stepId)}/attempts/${attempt}/logs?${params.toString()}`,\n {signal},\n );\n return toLogRead(response);\n}\n\nclass StepLogObjectFetchError extends Error {\n readonly status: number;\n\n constructor(status: number) {\n super(`Could not load compacted logs (${status})`);\n this.name = 'StepLogObjectFetchError';\n this.status = status;\n }\n}\n\nasync function readPresignedLogObject(url: string, signal?: AbortSignal): Promise<string> {\n const response = await fetch(url, signal ? {signal} : undefined);\n if (!response.ok) throw new StepLogObjectFetchError(response.status);\n return await response.text();\n}\n\nexport function isMissingStepLogStreamError(error: unknown): boolean {\n return error instanceof ApiError && error.status === 404 && error.code === 'not-found';\n}\n\nexport interface UseStepAttemptLogsQueryOptions {\n retryMissingStream?: boolean;\n missingStreamRetryCount?: number | undefined;\n missingStreamRetryDelayMs?: number | undefined;\n initialErrorRetryCount?: number;\n initialErrorRetryDelayMs?: number;\n}\n\n/**\n * Narrow React Query wrapper for step logs. Its query function can read the prior\n * snapshot imperatively, but bounded missing-stream retries need a ref that resets\n * when the step, attempt, or retry budget changes. Keep that lifecycle state here\n * rather than exporting query options that could accidentally share it between views.\n */\nexport function useStepAttemptLogsQuery(\n stepId: string | undefined,\n attempt: number | undefined,\n options: UseStepAttemptLogsQueryOptions = {},\n) {\n const queryClient = useQueryClient();\n const missingStreamFailureCountRef = useRef(0);\n const missingStreamScopeRef = useRef<string | null>(null);\n const manualRefetchRef = useRef(false);\n const enabled = Boolean(stepId && attempt && Number.isInteger(attempt) && attempt > 0);\n const queryKey =\n enabled && stepId && attempt\n ? stepLogsQueryKeys.detail(stepId, attempt)\n : [...stepLogsQueryKeys.all, 'detail'];\n const missingStreamScope =\n enabled && stepId && attempt\n ? `${stepId}:${attempt}:${options.missingStreamRetryCount ?? 'unbounded'}`\n : null;\n if (missingStreamScopeRef.current !== missingStreamScope) {\n missingStreamScopeRef.current = missingStreamScope;\n missingStreamFailureCountRef.current = 0;\n }\n const initialErrorRetryCount = options.initialErrorRetryCount ?? 0;\n const initialErrorRetryDelayMs = options.initialErrorRetryDelayMs ?? STEP_LOG_LIVE_REFETCH_MS;\n const missingStreamRetryDelayMs = options.missingStreamRetryDelayMs ?? STEP_LOG_LIVE_REFETCH_MS;\n\n const query = useQuery({\n queryKey,\n enabled,\n queryFn: async ({signal}) => {\n const previous = queryClient.getQueryData<StepLogSnapshot>(queryKey);\n const manualRefetch = manualRefetchRef.current;\n manualRefetchRef.current = false;\n let response: Awaited<ReturnType<typeof readStepAttemptLogsPage>>;\n try {\n response = await readStepAttemptLogsPage({\n stepId: stepId ?? '',\n attempt: attempt ?? 0,\n cursor: previous?.nextCursor ?? 0,\n signal,\n });\n } catch (error) {\n if (\n options.retryMissingStream &&\n previous === undefined &&\n isMissingStepLogStreamError(error) &&\n !manualRefetch\n ) {\n const retryCount = options.missingStreamRetryCount;\n if (retryCount === undefined) throw error;\n if (missingStreamFailureCountRef.current >= retryCount) {\n return emptyCompleteLogSnapshot();\n }\n missingStreamFailureCountRef.current += 1;\n }\n throw error;\n }\n\n missingStreamFailureCountRef.current = 0;\n\n if (response.mode === 'presigned') {\n const ndjson = await readPresignedLogObject(response.url, signal);\n return mergeLogRead(previous, {\n mode: 'presigned',\n response,\n records: parseLogNdjson(ndjson),\n });\n }\n\n return mergeLogRead(previous, {\n mode: 'inline',\n response,\n records: parseLogNdjson(response.ndjson),\n });\n },\n retry: (failureCount, error) => {\n if (initialErrorRetryCount <= 0) return false;\n if (queryClient.getQueryData<StepLogSnapshot>(queryKey) !== undefined) return false;\n if (options.retryMissingStream && isMissingStepLogStreamError(error)) return false;\n return failureCount < initialErrorRetryCount;\n },\n retryDelay: initialErrorRetryDelayMs,\n refetchInterval: (query) => {\n if (\n options.retryMissingStream &&\n query.state.data === undefined &&\n isMissingStepLogStreamError(query.state.error)\n ) {\n return missingStreamRetryDelayMs;\n }\n\n return stepLogRefetchInterval(query.state.data, query.state.status === 'error');\n },\n refetchIntervalInBackground: false,\n refetchOnMount: (query) => !query.state.data?.complete,\n refetchOnWindowFocus: (query) => !query.state.data?.complete,\n refetchOnReconnect: (query) => !query.state.data?.complete,\n });\n\n const refetchLogs = useCallback(() => {\n // React Query reuses an in-flight request when it has no cached data. Only\n // mark a refetch as manual when this call can start a new request; otherwise\n // the marker would be consumed by the next automatic poll.\n if (!query.isFetching || query.data !== undefined) {\n manualRefetchRef.current = true;\n }\n return query.refetch();\n }, [query.data, query.isFetching, query.refetch]);\n\n return {...query, refetchLogs};\n}\n\nfunction emptyCompleteLogSnapshot(): StepLogSnapshot {\n return {\n records: [],\n nextCursor: 0,\n source: 'inline',\n state: 'closed',\n complete: true,\n hasMore: false,\n truncated: false,\n totalBytes: null,\n expiresAt: null,\n };\n}\n"],"names":["readLogsResponseSchema","ApiError","checkedApiRequest","useQuery","useQueryClient","useCallback","useRef","mergeLogRead","STEP_LOG_LIVE_REFETCH_MS","stepLogRefetchInterval","parseLogNdjson","toLogRead","stepLogsQueryKeys","all","detail","stepId","attempt","readStepAttemptLogsPage","cursor","signal","params","URLSearchParams","String","response","encodeURIComponent","toString","StepLogObjectFetchError","Error","status","name","readPresignedLogObject","url","fetch","undefined","ok","text","isMissingStepLogStreamError","error","code","useStepAttemptLogsQuery","options","queryClient","missingStreamFailureCountRef","missingStreamScopeRef","manualRefetchRef","enabled","Boolean","Number","isInteger","queryKey","missingStreamScope","missingStreamRetryCount","current","initialErrorRetryCount","initialErrorRetryDelayMs","missingStreamRetryDelayMs","query","queryFn","previous","getQueryData","manualRefetch","nextCursor","retryMissingStream","retryCount","emptyCompleteLogSnapshot","mode","ndjson","records","retry","failureCount","retryDelay","refetchInterval","state","data","refetchIntervalInBackground","refetchOnMount","complete","refetchOnWindowFocus","refetchOnReconnect","refetchLogs","isFetching","refetch","source","hasMore","truncated","totalBytes","expiresAt"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/api/step-logs.ts"],"sourcesContent":["import {readLogsResponseSchema} from '@shipfox/api-logs-dto';\nimport {ApiError, checkedApiRequest} from '@shipfox/client-api';\nimport {useQuery, useQueryClient} from '@tanstack/react-query';\nimport {type RefObject, useCallback, useRef} from 'react';\nimport {\n mergeLogRead,\n STEP_LOG_LIVE_REFETCH_MS,\n type StepLogSnapshot,\n stepLogRefetchInterval,\n} from '#core/log-read.js';\nimport {parseLogNdjson, toLogRead} from './log-mapper.js';\n\nexport const stepLogsQueryKeys = {\n all: ['step-logs'] as const,\n detail: (stepId: string, attempt: number) =>\n [...stepLogsQueryKeys.all, 'detail', stepId, attempt] as const,\n};\n\ninterface ReadStepAttemptLogsPageParams {\n stepId: string;\n attempt: number;\n cursor: number;\n signal?: AbortSignal;\n}\n\nexport async function readStepAttemptLogsPage({\n stepId,\n attempt,\n cursor,\n signal,\n}: ReadStepAttemptLogsPageParams) {\n const params = new URLSearchParams({cursor: String(cursor)});\n const response = await checkedApiRequest(\n readLogsResponseSchema,\n `/steps/${encodeURIComponent(stepId)}/attempts/${attempt}/logs?${params.toString()}`,\n {signal},\n );\n return toLogRead(response);\n}\n\nclass StepLogObjectFetchError extends Error {\n readonly status: number;\n\n constructor(status: number) {\n super(`Could not load compacted logs (${status})`);\n this.name = 'StepLogObjectFetchError';\n this.status = status;\n }\n}\n\nasync function readPresignedLogObject(url: string, signal?: AbortSignal): Promise<string> {\n const response = await fetch(url, signal ? {signal} : undefined);\n if (!response.ok) throw new StepLogObjectFetchError(response.status);\n return await response.text();\n}\n\nexport function isMissingStepLogStreamError(error: unknown): boolean {\n return error instanceof ApiError && error.status === 404 && error.code === 'not-found';\n}\n\nexport interface UseStepAttemptLogsQueryOptions {\n retryMissingStream?: boolean;\n missingStreamRetryCount?: number | undefined;\n missingStreamRetryDelayMs?: number | undefined;\n initialErrorRetryCount?: number;\n initialErrorRetryDelayMs?: number;\n}\n\n/**\n * Narrow React Query wrapper for step logs. Its query function can read the prior\n * snapshot imperatively, but bounded missing-stream retries need a ref that resets\n * when the step, attempt, or retry budget changes. Keep that lifecycle state here\n * rather than exporting query options that could accidentally share it between views.\n */\nexport function useStepAttemptLogsQuery(\n stepId: string | undefined,\n attempt: number | undefined,\n options: UseStepAttemptLogsQueryOptions = {},\n) {\n const queryClient = useQueryClient();\n const missingStreamFailureCountRef = useRef(0);\n const missingStreamScopeRef = useRef<string | null>(null);\n const manualRefetchRef = useRef(false);\n const enabled = Boolean(stepId && attempt && Number.isInteger(attempt) && attempt > 0);\n const queryKey =\n enabled && stepId && attempt\n ? stepLogsQueryKeys.detail(stepId, attempt)\n : [...stepLogsQueryKeys.all, 'detail'];\n const missingStreamScope =\n enabled && stepId && attempt\n ? `${stepId}:${attempt}:${options.missingStreamRetryCount ?? 'unbounded'}`\n : null;\n if (missingStreamScopeRef.current !== missingStreamScope) {\n missingStreamScopeRef.current = missingStreamScope;\n missingStreamFailureCountRef.current = 0;\n }\n const initialErrorRetryCount = options.initialErrorRetryCount ?? 0;\n const initialErrorRetryDelayMs = options.initialErrorRetryDelayMs ?? STEP_LOG_LIVE_REFETCH_MS;\n const missingStreamRetryDelayMs = options.missingStreamRetryDelayMs ?? STEP_LOG_LIVE_REFETCH_MS;\n\n const query = useQuery({\n queryKey,\n enabled,\n queryFn: ({signal}) =>\n loadStepLogsQuery({\n signal,\n stepId,\n attempt,\n queryKey,\n queryClient,\n options,\n manualRefetchRef,\n missingStreamFailureCountRef,\n }),\n retry: (failureCount, error) => {\n if (initialErrorRetryCount <= 0) return false;\n if (queryClient.getQueryData<StepLogSnapshot>(queryKey) !== undefined) return false;\n if (options.retryMissingStream && isMissingStepLogStreamError(error)) return false;\n return failureCount < initialErrorRetryCount;\n },\n retryDelay: initialErrorRetryDelayMs,\n refetchInterval: (query) => {\n if (\n options.retryMissingStream &&\n query.state.data === undefined &&\n isMissingStepLogStreamError(query.state.error)\n ) {\n return missingStreamRetryDelayMs;\n }\n\n return stepLogRefetchInterval(query.state.data, query.state.status === 'error');\n },\n refetchIntervalInBackground: false,\n refetchOnMount: (query) => !query.state.data?.complete,\n refetchOnWindowFocus: (query) => !query.state.data?.complete,\n refetchOnReconnect: (query) => !query.state.data?.complete,\n });\n\n const refetchLogs = useCallback(() => {\n // React Query reuses an in-flight request when it has no cached data. Only\n // mark a refetch as manual when this call can start a new request; otherwise\n // the marker would be consumed by the next automatic poll.\n if (!query.isFetching || query.data !== undefined) {\n manualRefetchRef.current = true;\n }\n return query.refetch();\n }, [query.data, query.isFetching, query.refetch]);\n\n return {...query, refetchLogs};\n}\n\nasync function loadStepLogsQuery(params: {\n signal: AbortSignal;\n stepId: string | undefined;\n attempt: number | undefined;\n queryKey: readonly unknown[];\n queryClient: ReturnType<typeof useQueryClient>;\n options: UseStepAttemptLogsQueryOptions;\n manualRefetchRef: RefObject<boolean>;\n missingStreamFailureCountRef: RefObject<number>;\n}): Promise<StepLogSnapshot> {\n const previous = params.queryClient.getQueryData<StepLogSnapshot>(params.queryKey);\n const manualRefetch = params.manualRefetchRef.current;\n params.manualRefetchRef.current = false;\n let response: Awaited<ReturnType<typeof readStepAttemptLogsPage>>;\n try {\n response = await readStepAttemptLogsPage({\n stepId: params.stepId ?? '',\n attempt: params.attempt ?? 0,\n cursor: previous?.nextCursor ?? 0,\n signal: params.signal,\n });\n } catch (error) {\n return handleMissingStreamLoadError(error, previous, manualRefetch, params);\n }\n params.missingStreamFailureCountRef.current = 0;\n if (response.mode === 'presigned') {\n const ndjson = await readPresignedLogObject(response.url, params.signal);\n return mergeLogRead(previous, {\n mode: 'presigned',\n response,\n records: parseLogNdjson(ndjson),\n });\n }\n return mergeLogRead(previous, {\n mode: 'inline',\n response,\n records: parseLogNdjson(response.ndjson),\n });\n}\n\nfunction handleMissingStreamLoadError(\n error: unknown,\n previous: StepLogSnapshot | undefined,\n manualRefetch: boolean,\n params: Pick<Parameters<typeof loadStepLogsQuery>[0], 'options' | 'missingStreamFailureCountRef'>,\n): never | StepLogSnapshot {\n const retryable =\n params.options.retryMissingStream &&\n previous === undefined &&\n isMissingStepLogStreamError(error) &&\n !manualRefetch;\n if (!retryable) throw error;\n const retryCount = params.options.missingStreamRetryCount;\n if (retryCount === undefined) throw error;\n if (params.missingStreamFailureCountRef.current >= retryCount) {\n return emptyCompleteLogSnapshot();\n }\n params.missingStreamFailureCountRef.current += 1;\n throw error;\n}\n\nfunction emptyCompleteLogSnapshot(): StepLogSnapshot {\n return {\n records: [],\n nextCursor: 0,\n source: 'inline',\n state: 'closed',\n complete: true,\n hasMore: false,\n truncated: false,\n totalBytes: null,\n expiresAt: null,\n };\n}\n"],"names":["readLogsResponseSchema","ApiError","checkedApiRequest","useQuery","useQueryClient","useCallback","useRef","mergeLogRead","STEP_LOG_LIVE_REFETCH_MS","stepLogRefetchInterval","parseLogNdjson","toLogRead","stepLogsQueryKeys","all","detail","stepId","attempt","readStepAttemptLogsPage","cursor","signal","params","URLSearchParams","String","response","encodeURIComponent","toString","StepLogObjectFetchError","Error","status","name","readPresignedLogObject","url","fetch","undefined","ok","text","isMissingStepLogStreamError","error","code","useStepAttemptLogsQuery","options","queryClient","missingStreamFailureCountRef","missingStreamScopeRef","manualRefetchRef","enabled","Boolean","Number","isInteger","queryKey","missingStreamScope","missingStreamRetryCount","current","initialErrorRetryCount","initialErrorRetryDelayMs","missingStreamRetryDelayMs","query","queryFn","loadStepLogsQuery","retry","failureCount","getQueryData","retryMissingStream","retryDelay","refetchInterval","state","data","refetchIntervalInBackground","refetchOnMount","complete","refetchOnWindowFocus","refetchOnReconnect","refetchLogs","isFetching","refetch","previous","manualRefetch","nextCursor","handleMissingStreamLoadError","mode","ndjson","records","retryable","retryCount","emptyCompleteLogSnapshot","source","hasMore","truncated","totalBytes","expiresAt"],"mappings":"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"}
|