@@ -88,9 +88,14 @@ type CustomTypesMachineManagerDeleteCustomTypeReturnType = {
8888 errors : ( DecodeError | HookError ) [ ] ;
8989} ;
9090
91- type CustomTypeFieldIdChangedMeta = NonNullable <
92- NonNullable < CustomTypeUpdateHookData [ "updateMeta" ] > [ "fieldIdChanged" ]
93- > ;
91+ type CustomTypesMachineManagerUpdateCustomTypeReturnType = {
92+ errors : ( DecodeError | HookError ) [ ] ;
93+ } ;
94+
95+ type CustomTypeFieldIdChangedMeta = {
96+ previousPath : string [ ] ;
97+ newPath : string [ ] ;
98+ } ;
9499
95100type CrCustomType =
96101 | string
@@ -193,72 +198,95 @@ export class CustomTypesManager extends BaseManager {
193198 * property.
194199 */
195200 private async updateContentRelationships (
196- args : CustomTypeUpdateHookData ,
197- ) : Promise < OnlyHookErrors < CallHookReturnType < CustomTypeUpdateHook > > > {
201+ args : { model : CustomType } & CustomTypeFieldIdChangedMeta ,
202+ ) : Promise <
203+ OnlyHookErrors < CallHookReturnType < CustomTypeUpdateHook > > & {
204+ rollback ?: ( ) => Promise < void > ;
205+ }
206+ > {
198207 assertPluginsInitialized ( this . sliceMachinePluginRunner ) ;
199208
200- const { model, updateMeta } = args ;
201-
202- if ( updateMeta ?. fieldIdChanged ) {
203- let { previousPath, newPath } = updateMeta . fieldIdChanged ;
204-
205- if ( previousPath . join ( "." ) !== newPath . join ( "." ) ) {
206- previousPath = [ model . id , ...previousPath ] ;
207- newPath = [ model . id , ...newPath ] ;
209+ const { model } = args ;
210+ let { newPath, previousPath } = args ;
211+
212+ if ( previousPath . join ( "." ) !== newPath . join ( "." ) ) {
213+ previousPath = [ model . id , ...previousPath ] ;
214+ newPath = [ model . id , ...newPath ] ;
215+
216+ const crUpdates : {
217+ updatePromise : Promise < { errors : HookError [ ] } > ;
218+ rollback : ( ) => void ;
219+ } [ ] = [ ] ;
220+
221+ // Find existing content relationships that link to the renamed field id in
222+ // any custom type and update them to use the new one.
223+ const customTypes = await this . readAllCustomTypes ( ) ;
224+
225+ updateCustomTypeContentRelationships ( {
226+ models : customTypes . models ,
227+ onUpdate : ( { previousModel, model : updatedModel } ) => {
228+ assertPluginsInitialized ( this . sliceMachinePluginRunner ) ;
229+
230+ crUpdates . push ( {
231+ updatePromise : this . sliceMachinePluginRunner ?. callHook (
232+ "custom-type:update" ,
233+ { model : updatedModel } ,
234+ ) ,
235+ rollback : ( ) => {
236+ this . sliceMachinePluginRunner ?. callHook ( "custom-type:update" , {
237+ model : previousModel ,
238+ } ) ;
239+ } ,
240+ } ) ;
241+ } ,
242+ previousPath,
243+ newPath,
244+ } ) ;
208245
209- const crUpdates : Promise < { errors : HookError [ ] } > [ ] = [ ] ;
246+ // Find existing slice with content relationships that link to the renamed
247+ // field id in all libraries and update them to use the new one.
248+ const { libraries } = await this . slices . readAllSliceLibraries ( ) ;
210249
211- // Find existing content relationships that link to the renamed field id in
212- // any custom type and update them to use the new one.
213- const customTypes = await this . readAllCustomTypes ( ) ;
250+ for ( const library of libraries ) {
251+ const slices = await this . slices . readAllSlicesForLibrary ( {
252+ libraryID : library . libraryID ,
253+ } ) ;
214254
215- updateCustomTypeContentRelationships ( {
216- models : customTypes . models ,
217- onUpdate : ( model ) => {
218- pushIfDefined (
219- crUpdates ,
220- this . sliceMachinePluginRunner ?. callHook ( "custom-type:update" , {
221- model,
222- } ) ,
223- ) ;
255+ updateSharedSliceContentRelationships ( {
256+ models : slices . models ,
257+ onUpdate : ( { previousModel, model : updatedModel } ) => {
258+ assertPluginsInitialized ( this . sliceMachinePluginRunner ) ;
259+
260+ crUpdates . push ( {
261+ updatePromise : this . sliceMachinePluginRunner ?. callHook (
262+ "slice:update" ,
263+ { libraryID : library . libraryID , model : updatedModel } ,
264+ ) ,
265+ rollback : ( ) => {
266+ this . sliceMachinePluginRunner ?. callHook ( "slice:update" , {
267+ libraryID : library . libraryID ,
268+ model : previousModel ,
269+ } ) ;
270+ } ,
271+ } ) ;
224272 } ,
225273 previousPath,
226274 newPath,
227275 } ) ;
276+ }
228277
229- // Find existing slice with content relationships that link to the renamed
230- // field id in all libraries and update them to use the new one.
231- const { libraries } = await this . slices . readAllSliceLibraries ( ) ;
232-
233- for ( const library of libraries ) {
234- const slices = await this . slices . readAllSlicesForLibrary ( {
235- libraryID : library . libraryID ,
236- } ) ;
237-
238- updateSharedSliceContentRelationships ( {
239- models : slices . models ,
240- onUpdate : ( model ) => {
241- pushIfDefined (
242- crUpdates ,
243- this . sliceMachinePluginRunner ?. callHook ( "slice:update" , {
244- libraryID : library . libraryID ,
245- model,
246- } ) ,
247- ) ;
248- } ,
249- previousPath,
250- newPath,
251- } ) ;
252- }
253-
254- // Process all the Content Relationship updates at once.
255- const crUpdatesResult = await Promise . all ( crUpdates ) ;
278+ // Process all the Content Relationship updates at once.
279+ const crUpdatesResult = await Promise . all (
280+ crUpdates . map ( ( update ) => update . updatePromise ) ,
281+ ) ;
256282
257- if ( crUpdatesResult . some ( ( result ) => result . errors . length > 0 ) ) {
258- return {
259- errors : crUpdatesResult . flatMap ( ( result ) => result . errors ) ,
260- } ;
261- }
283+ if ( crUpdatesResult . some ( ( result ) => result . errors . length > 0 ) ) {
284+ return {
285+ errors : crUpdatesResult . flatMap ( ( result ) => result . errors ) ,
286+ rollback : async ( ) => {
287+ await Promise . all ( crUpdates . map ( ( update ) => update . rollback ( ) ) ) ;
288+ } ,
289+ } ;
262290 }
263291 }
264292
@@ -267,19 +295,56 @@ export class CustomTypesManager extends BaseManager {
267295
268296 async updateCustomType (
269297 args : CustomTypeUpdateHookData ,
270- ) : Promise < OnlyHookErrors < CallHookReturnType < CustomTypeUpdateHook > > > {
298+ ) : Promise < CustomTypesMachineManagerUpdateCustomTypeReturnType > {
271299 assertPluginsInitialized ( this . sliceMachinePluginRunner ) ;
300+ const { model } = args ;
301+ const { fieldIdChanged } = args . updateMeta ?? { } ;
272302
273- const hookResult = await this . sliceMachinePluginRunner . callHook (
303+ let previousCustomType : CustomType | undefined ;
304+
305+ if ( fieldIdChanged ) {
306+ const customTypeRead = await this . readCustomType ( { id : model . id } ) ;
307+
308+ if ( customTypeRead . errors . length > 0 ) {
309+ return { errors : customTypeRead . errors } ;
310+ }
311+ if ( ! customTypeRead . model ) {
312+ throw new Error (
313+ `readCustomType succeeded reading custom type ${ model . id } but model is undefined.` ,
314+ ) ;
315+ }
316+
317+ previousCustomType = customTypeRead . model ;
318+ }
319+
320+ const customTypeUpdateResult = await this . sliceMachinePluginRunner . callHook (
274321 "custom-type:update" ,
275- args ,
322+ { model } ,
276323 ) ;
277324
278- if ( args . updateMeta ?. fieldIdChanged ) {
279- await this . updateContentRelationships ( args ) ;
325+ if ( customTypeUpdateResult . errors . length > 0 ) {
326+ return { errors : customTypeUpdateResult . errors } ;
280327 }
281328
282- return { errors : hookResult . errors } ;
329+ if ( previousCustomType && fieldIdChanged ) {
330+ const crUpdateResult = await this . updateContentRelationships ( {
331+ ...fieldIdChanged ,
332+ model : previousCustomType ,
333+ } ) ;
334+
335+ if ( crUpdateResult . errors . length > 0 ) {
336+ // put the previous custom type back
337+ await this . sliceMachinePluginRunner ?. callHook ( "custom-type:update" , {
338+ model : previousCustomType ,
339+ } ) ;
340+ // revert the content relationships updates
341+ await crUpdateResult . rollback ?.( ) ;
342+
343+ return { errors : crUpdateResult . errors } ;
344+ }
345+ }
346+
347+ return { errors : [ ] } ;
283348 }
284349
285350 async renameCustomType (
@@ -620,13 +685,13 @@ function updateFieldContentRelationships<
620685export function updateCustomTypeContentRelationships (
621686 args : {
622687 models : { model : CustomType } [ ] ;
623- onUpdate : ( model : CustomType ) => void ;
688+ onUpdate : ( model : { previousModel : CustomType ; model : CustomType } ) => void ;
624689 } & CustomTypeFieldIdChangedMeta ,
625690) : void {
626691 const { models, previousPath, newPath, onUpdate } = args ;
627692
628693 for ( const { model : customType } of models ) {
629- const updatedCustomTypeModel = traverseCustomType ( {
694+ const updatedCustomType = traverseCustomType ( {
630695 customType,
631696 onField : ( { field } ) => {
632697 return updateFieldContentRelationships ( {
@@ -637,22 +702,25 @@ export function updateCustomTypeContentRelationships(
637702 } ,
638703 } ) ;
639704
640- if ( ! isEqualModel ( customType , updatedCustomTypeModel ) ) {
641- onUpdate ( updatedCustomTypeModel ) ;
705+ if ( ! isEqualModel ( customType , updatedCustomType ) ) {
706+ onUpdate ( { model : updatedCustomType , previousModel : customType } ) ;
642707 }
643708 }
644709}
645710
646711export function updateSharedSliceContentRelationships (
647712 args : {
648713 models : { model : SharedSlice } [ ] ;
649- onUpdate : ( model : SharedSlice ) => void ;
714+ onUpdate : ( model : {
715+ previousModel : SharedSlice ;
716+ model : SharedSlice ;
717+ } ) => void ;
650718 } & CustomTypeFieldIdChangedMeta ,
651719) : void {
652720 const { models, previousPath, newPath, onUpdate } = args ;
653721
654722 for ( const { model : slice } of models ) {
655- const updatedSliceModel = traverseSharedSlice ( {
723+ const updateSlice = traverseSharedSlice ( {
656724 path : [ "." ] ,
657725 slice,
658726 onField : ( { field } ) => {
@@ -664,8 +732,8 @@ export function updateSharedSliceContentRelationships(
664732 } ,
665733 } ) ;
666734
667- if ( ! isEqualModel ( slice , updatedSliceModel ) ) {
668- onUpdate ( updatedSliceModel ) ;
735+ if ( ! isEqualModel ( slice , updateSlice ) ) {
736+ onUpdate ( { model : updateSlice , previousModel : slice } ) ;
669737 }
670738 }
671739}
@@ -684,9 +752,3 @@ function shallowCloneIfObject<T>(value: T): T {
684752
685753 return value ;
686754}
687-
688- function pushIfDefined < T > ( array : T [ ] , value : T | undefined ) {
689- if ( value ) {
690- array . push ( value ) ;
691- }
692- }
0 commit comments