@@ -71,76 +71,20 @@ export async function executeBulkOperation(input: ExecuteBulkOperationInput): Pr
7171 const finalOperation = await renderSingleTask < BulkOperation > ( {
7272 title : outputContent `Starting bulk operation...` ,
7373 task : async ( updateStatus ) => {
74- let lastObjectCount = ''
75- while ( true ) {
76- // eslint-disable-next-line no-await-in-loop
77- const response = await adminRequestDoc < GetBulkOperationByIdQuery , { id : string } > ( {
78- query : GetBulkOperationById ,
79- session : adminSession ,
80- variables : { id : result . id } ,
81- version : '2026-01' ,
82- } )
83-
84- if ( ! response . bulkOperation ) {
85- throw new Error ( 'bulk operation not found' )
86- }
87-
88- const op = response . bulkOperation as BulkOperation
89-
90- if ( op . status === 'RUNNING' ) {
91- const currentObjectCount = String ( op . objectCount )
92- if ( currentObjectCount !== lastObjectCount ) {
93- updateStatus ( formatBulkOperationStatus ( op ) )
94- lastObjectCount = currentObjectCount
95- }
96- } else if ( op . status === 'CREATED' ) {
97- updateStatus ( formatBulkOperationStatus ( op ) )
98- }
99-
100- if ( TERMINAL_STATUSES . includes ( op . status ) ) {
101- return op
102- }
74+ const generator = watchBulkOperation ( adminSession , result . id )
10375
76+ while ( true ) {
10477 // eslint-disable-next-line no-await-in-loop
105- await sleep ( POLL_INTERVAL_SECONDS )
78+ const { value : latestOperationState , done} = await generator . next ( )
79+ if ( done ) return latestOperationState
80+ updateStatus ( formatBulkOperationStatus ( latestOperationState ) )
10681 }
10782 } ,
10883 } )
10984
110- const url = finalOperation . url ?? ''
111-
112- renderSuccess ( {
113- headline : 'Bulk operation complete!' ,
114- body : outputContent `ID: ${ outputToken . cyan ( finalOperation . id ) } \nStatus: ${ outputToken . yellow (
115- finalOperation . status ,
116- ) } \nObject count: ${ outputToken . gray (
117- String ( finalOperation . objectCount ) ,
118- ) } \n\nDownload results ${ outputToken . link ( 'here.' , url ) } `. value ,
119- } )
85+ renderBulkOperationComplete ( finalOperation )
12086 } else {
121- const infoSections = [
122- {
123- title : 'Bulk operation started!' ,
124- body : [
125- {
126- list : {
127- items : [
128- outputContent `ID: ${ outputToken . cyan ( result . id ) } ` . value ,
129- outputContent `Status: ${ outputToken . yellow ( result . status ) } ` . value ,
130- outputContent `Created: ${ outputToken . gray ( String ( result . createdAt ) ) } ` . value ,
131- ] ,
132- } ,
133- } ,
134- ] ,
135- } ,
136- ]
137-
138- renderInfo ( { customSections : infoSections } )
139-
140- renderSuccess ( {
141- headline : 'Bulk operation started successfully!' ,
142- body : 'Congrats!' ,
143- } )
87+ renderBulkOperationStarted ( result )
14488 }
14589 }
14690}
@@ -151,3 +95,83 @@ function isMutation(graphqlOperation: string): boolean {
15195
15296 return firstOperation ?. kind === 'OperationDefinition' && firstOperation . operation === 'mutation'
15397}
98+
99+ async function * watchBulkOperation (
100+ adminSession : ReturnType < typeof ensureAuthenticatedAdmin > extends Promise < infer T > ? T : never ,
101+ operationId : string ,
102+ ) : AsyncGenerator < BulkOperation , BulkOperation > {
103+ let lastObjectCount = ''
104+
105+ while ( true ) {
106+ // eslint-disable-next-line no-await-in-loop
107+ const response = await adminRequestDoc < GetBulkOperationByIdQuery , { id : string } > ( {
108+ query : GetBulkOperationById ,
109+ session : adminSession ,
110+ variables : { id : operationId } ,
111+ version : '2026-01' ,
112+ } )
113+
114+ if ( ! response . bulkOperation ) {
115+ throw new Error ( 'bulk operation not found' )
116+ }
117+
118+ const op = response . bulkOperation as BulkOperation
119+
120+ const shouldYield =
121+ op . status === 'CREATED' || ( op . status === 'RUNNING' && String ( op . objectCount ) !== lastObjectCount )
122+
123+ if ( shouldYield ) {
124+ if ( op . status === 'RUNNING' ) {
125+ lastObjectCount = String ( op . objectCount )
126+ }
127+ yield op
128+ }
129+
130+ if ( TERMINAL_STATUSES . includes ( op . status ) ) {
131+ return op
132+ }
133+
134+ // eslint-disable-next-line no-await-in-loop
135+ await sleep ( POLL_INTERVAL_SECONDS )
136+ }
137+ }
138+
139+ function renderBulkOperationComplete ( operation : BulkOperation ) : void {
140+ const url = operation . url ?? ''
141+
142+ renderSuccess ( {
143+ headline : 'Bulk operation complete!' ,
144+ body : outputContent `ID: ${ outputToken . cyan ( operation . id ) } \nStatus: ${ outputToken . yellow (
145+ operation . status ,
146+ ) } \nObject count: ${ outputToken . gray ( String ( operation . objectCount ) ) } \n\nDownload results ${ outputToken . link (
147+ 'here.' ,
148+ url ,
149+ ) } `. value ,
150+ } )
151+ }
152+
153+ function renderBulkOperationStarted ( result : { id : string ; status : string ; createdAt : unknown } ) : void {
154+ const infoSections = [
155+ {
156+ title : 'Bulk operation started!' ,
157+ body : [
158+ {
159+ list : {
160+ items : [
161+ outputContent `ID: ${ outputToken . cyan ( result . id ) } ` . value ,
162+ outputContent `Status: ${ outputToken . yellow ( result . status ) } ` . value ,
163+ outputContent `Created: ${ outputToken . gray ( String ( result . createdAt ) ) } ` . value ,
164+ ] ,
165+ } ,
166+ } ,
167+ ] ,
168+ } ,
169+ ]
170+
171+ renderInfo ( { customSections : infoSections } )
172+
173+ renderSuccess ( {
174+ headline : 'Bulk operation started successfully!' ,
175+ body : 'Congrats!' ,
176+ } )
177+ }
0 commit comments