@slicemachine/manager 0.24.14-alpha.jp-update-cr-links-delete-field-data-flow.12 → 0.24.14-alpha.jp-update-cr-links-rollback.2

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.
@@ -88,11 +88,14 @@ type CustomTypesMachineManagerDeleteCustomTypeReturnType = {
88
88
  errors: (DecodeError | HookError)[];
89
89
  };
90
90
 
91
- type CustomTypeFieldUpdatedPaths = {
92
- previousPath: string[];
93
- newPath: string[];
91
+ type CustomTypesMachineManagerUpdateCustomTypeReturnType = {
92
+ errors: (DecodeError | HookError)[];
94
93
  };
95
94
 
95
+ type CustomTypeFieldIdChangedMeta = NonNullable<
96
+ NonNullable<CustomTypeUpdateHookData["updateMeta"]>["fieldIdChanged"]
97
+ >;
98
+
96
99
  type CrCustomType =
97
100
  | string
98
101
  | { id: string; fields?: readonly CrCustomTypeNestedCr[] };
@@ -190,96 +193,166 @@ export class CustomTypesManager extends BaseManager {
190
193
 
191
194
  /**
192
195
  * Update the Content Relationship API IDs for all existing custom types and
193
- * slices. The change is determined by properties inside the `updated`
196
+ * slices. The change is determined by properties inside the `updateMeta`
194
197
  * property.
195
198
  */
196
199
  private async updateContentRelationships(
197
200
  args: CustomTypeUpdateHookData,
198
- ): Promise<OnlyHookErrors<CallHookReturnType<CustomTypeUpdateHook>>> {
201
+ ): Promise<
202
+ OnlyHookErrors<CallHookReturnType<CustomTypeUpdateHook>> & {
203
+ rollback: () => Promise<void>;
204
+ }
205
+ > {
199
206
  assertPluginsInitialized(this.sliceMachinePluginRunner);
200
207
 
201
- const { model, updates } = args;
208
+ const { model, updateMeta } = args;
202
209
 
203
- if (updates) {
204
- for (const [previousPathStr, newPathStr] of Object.entries(updates)) {
205
- if (previousPathStr !== newPathStr) {
206
- const previousPath = [model.id, ...previousPathStr.split(".")];
207
- const newPath = [model.id, ...newPathStr.split(".")];
208
- const crUpdates: Promise<{ errors: HookError[] }>[] = [];
210
+ if (updateMeta?.fieldIdChanged) {
211
+ let { previousPath, newPath } = updateMeta.fieldIdChanged;
209
212
 
210
- // Find existing content relationships that link to the renamed field
211
- // id in any custom type and update them to use the new one.
212
- const customTypes = await this.readAllCustomTypes();
213
+ if (previousPath.join(".") !== newPath.join(".")) {
214
+ previousPath = [model.id, ...previousPath];
215
+ newPath = [model.id, ...newPath];
213
216
 
214
- updateCustomTypeContentRelationships({
215
- models: customTypes.models,
216
- onUpdate: (model) => {
217
- pushIfDefined(
218
- crUpdates,
219
- this.sliceMachinePluginRunner?.callHook("custom-type:update", {
220
- model,
221
- }),
222
- );
223
- },
224
- previousPath,
225
- newPath,
226
- });
217
+ const crUpdates: {
218
+ updatePromise: Promise<{ errors: HookError[] }>;
219
+ rollback: () => void;
220
+ }[] = [];
221
+
222
+ // Find existing content relationships that link to the renamed field id in
223
+ // any custom type and update them to use the new one.
224
+ const customTypes = await this.readAllCustomTypes();
227
225
 
228
- // Find existing slice with content relationships that link to the
229
- // renamed field id in all libraries and update them to use the new one.
230
- const { libraries } = await this.slices.readAllSliceLibraries();
226
+ updateCustomTypeContentRelationships({
227
+ models: customTypes.models,
228
+ onUpdate: ({ previousModel, model }) => {
229
+ assertPluginsInitialized(this.sliceMachinePluginRunner);
231
230
 
232
- for (const library of libraries) {
233
- const slices = await this.slices.readAllSlicesForLibrary({
234
- libraryID: library.libraryID,
231
+ crUpdates.push({
232
+ updatePromise: this.sliceMachinePluginRunner?.callHook(
233
+ "custom-type:update",
234
+ { model },
235
+ ),
236
+ rollback: () => {
237
+ this.sliceMachinePluginRunner?.callHook("custom-type:update", {
238
+ model: previousModel,
239
+ });
240
+ },
235
241
  });
242
+ },
243
+ previousPath,
244
+ newPath,
245
+ });
236
246
 
237
- updateSharedSliceContentRelationships({
238
- models: slices.models,
239
- onUpdate: (model) => {
240
- pushIfDefined(
241
- crUpdates,
247
+ // Find existing slice with content relationships that link to the renamed
248
+ // field id in all libraries and update them to use the new one.
249
+ const { libraries } = await this.slices.readAllSliceLibraries();
250
+
251
+ for (const library of libraries) {
252
+ const slices = await this.slices.readAllSlicesForLibrary({
253
+ libraryID: library.libraryID,
254
+ });
255
+
256
+ updateSharedSliceContentRelationships({
257
+ models: slices.models,
258
+ onUpdate: ({ previousModel, model }) => {
259
+ assertPluginsInitialized(this.sliceMachinePluginRunner);
260
+
261
+ crUpdates.push({
262
+ updatePromise: this.sliceMachinePluginRunner?.callHook(
263
+ "slice:update",
264
+ { libraryID: library.libraryID, model },
265
+ ),
266
+ rollback: () => {
242
267
  this.sliceMachinePluginRunner?.callHook("slice:update", {
243
268
  libraryID: library.libraryID,
244
- model,
245
- }),
246
- );
247
- },
248
- previousPath,
249
- newPath,
250
- });
251
- }
269
+ model: previousModel,
270
+ });
271
+ },
272
+ });
273
+ },
274
+ previousPath,
275
+ newPath,
276
+ });
277
+ }
252
278
 
253
- // Process all the Content Relationship updates at once.
254
- const crUpdatesResult = await Promise.all(crUpdates);
279
+ // Process all the Content Relationship updates at once.
280
+ const crUpdatesResult = await Promise.all(
281
+ crUpdates.map((update) => update.updatePromise),
282
+ );
255
283
 
256
- if (crUpdatesResult.some((result) => result.errors.length > 0)) {
257
- return {
258
- errors: crUpdatesResult.flatMap((result) => result.errors),
259
- };
260
- }
284
+ if (crUpdatesResult.some((result) => result.errors.length > 0)) {
285
+ return {
286
+ errors: crUpdatesResult.flatMap((result) => result.errors),
287
+ rollback: async () => {
288
+ await Promise.all(crUpdates.map((update) => update.rollback()));
289
+ },
290
+ };
261
291
  }
262
292
  }
263
293
  }
264
294
 
265
- return { errors: [] };
295
+ return { errors: [], rollback: () => Promise.resolve() };
266
296
  }
267
297
 
268
298
  async updateCustomType(
269
299
  args: CustomTypeUpdateHookData,
270
- ): Promise<OnlyHookErrors<CallHookReturnType<CustomTypeUpdateHook>>> {
300
+ ): Promise<CustomTypesMachineManagerUpdateCustomTypeReturnType> {
271
301
  assertPluginsInitialized(this.sliceMachinePluginRunner);
302
+ const { model } = args;
272
303
 
273
- const hookResult = await this.sliceMachinePluginRunner.callHook(
304
+ let updateCrPromise: (() => Promise<{ errors: HookError[] }>) | undefined;
305
+
306
+ if (args.updateMeta?.fieldIdChanged) {
307
+ const customTypeRead = await this.readCustomType({ id: model.id });
308
+
309
+ if (customTypeRead.errors.length > 0) {
310
+ return { errors: customTypeRead.errors };
311
+ }
312
+ if (!customTypeRead.model) {
313
+ throw new Error(
314
+ "Read custom type without errors, but model is undefined.",
315
+ );
316
+ }
317
+
318
+ const previousCustomType = customTypeRead.model;
319
+
320
+ updateCrPromise = async () => {
321
+ const crUpdateResult = await this.updateContentRelationships(args);
322
+
323
+ if (crUpdateResult.errors.length > 0) {
324
+ // put the previous custom type back
325
+ await this.sliceMachinePluginRunner?.callHook("custom-type:update", {
326
+ model: previousCustomType,
327
+ });
328
+ // revert the content relationships updates
329
+ await crUpdateResult.rollback();
330
+
331
+ return { errors: crUpdateResult.errors };
332
+ }
333
+
334
+ return { errors: [] };
335
+ };
336
+ }
337
+
338
+ // Execute the updates
339
+
340
+ const customTypeUpdateResult = await this.sliceMachinePluginRunner.callHook(
274
341
  "custom-type:update",
275
- args,
342
+ { model },
276
343
  );
277
344
 
278
- if (args.updates) {
279
- await this.updateContentRelationships(args);
345
+ if (customTypeUpdateResult.errors.length > 0) {
346
+ return { errors: customTypeUpdateResult.errors };
347
+ }
348
+
349
+ const crUpdateResult = await updateCrPromise?.();
350
+
351
+ if (crUpdateResult && crUpdateResult.errors.length > 0) {
352
+ return { errors: crUpdateResult.errors };
280
353
  }
281
354
 
282
- return { errors: hookResult.errors };
355
+ return { errors: [] };
283
356
  }
284
357
 
285
358
  async renameCustomType(
@@ -495,7 +568,7 @@ const InferSliceResponse = z.object({
495
568
  });
496
569
 
497
570
  function updateCRCustomType(
498
- args: { customType: CrCustomType } & CustomTypeFieldUpdatedPaths,
571
+ args: { customType: CrCustomType } & CustomTypeFieldIdChangedMeta,
499
572
  ): CrCustomType {
500
573
  const [previousCustomTypeId, previousFieldId] = args.previousPath;
501
574
  const [newCustomTypeId, newFieldId] = args.newPath;
@@ -596,8 +669,8 @@ function updateCRCustomType(
596
669
  */
597
670
  function updateFieldContentRelationships<
598
671
  T extends UID | NestableWidget | Group | NestedGroup,
599
- >(args: { field: T } & CustomTypeFieldUpdatedPaths): T {
600
- const { field, ...updatedPaths } = args;
672
+ >(args: { field: T } & CustomTypeFieldIdChangedMeta): T {
673
+ const { field, ...updateMeta } = args;
601
674
  if (
602
675
  field.type !== "Link" ||
603
676
  field.config?.select !== "document" ||
@@ -608,7 +681,7 @@ function updateFieldContentRelationships<
608
681
  }
609
682
 
610
683
  const newCustomTypes = field.config.customtypes.map((customType) => {
611
- return updateCRCustomType({ customType, ...updatedPaths });
684
+ return updateCRCustomType({ customType, ...updateMeta });
612
685
  });
613
686
 
614
687
  return {
@@ -620,21 +693,25 @@ function updateFieldContentRelationships<
620
693
  export function updateCustomTypeContentRelationships(
621
694
  args: {
622
695
  models: { model: CustomType }[];
623
- onUpdate: (model: CustomType) => void;
624
- } & CustomTypeFieldUpdatedPaths,
696
+ onUpdate: (model: { previousModel: CustomType; model: CustomType }) => void;
697
+ } & CustomTypeFieldIdChangedMeta,
625
698
  ): void {
626
- const { models, onUpdate, ...updatedPaths } = args;
699
+ const { models, previousPath, newPath, onUpdate } = args;
627
700
 
628
701
  for (const { model: customType } of models) {
629
- const updatedCustomTypeModel = traverseCustomType({
702
+ const updatedCustomType = traverseCustomType({
630
703
  customType,
631
704
  onField: ({ field }) => {
632
- return updateFieldContentRelationships({ ...updatedPaths, field });
705
+ return updateFieldContentRelationships({
706
+ field,
707
+ previousPath,
708
+ newPath,
709
+ });
633
710
  },
634
711
  });
635
712
 
636
- if (!isEqualModel(customType, updatedCustomTypeModel)) {
637
- onUpdate(updatedCustomTypeModel);
713
+ if (!isEqualModel(customType, updatedCustomType)) {
714
+ onUpdate({ model: updatedCustomType, previousModel: customType });
638
715
  }
639
716
  }
640
717
  }
@@ -642,22 +719,29 @@ export function updateCustomTypeContentRelationships(
642
719
  export function updateSharedSliceContentRelationships(
643
720
  args: {
644
721
  models: { model: SharedSlice }[];
645
- onUpdate: (model: SharedSlice) => void;
646
- } & CustomTypeFieldUpdatedPaths,
722
+ onUpdate: (model: {
723
+ previousModel: SharedSlice;
724
+ model: SharedSlice;
725
+ }) => void;
726
+ } & CustomTypeFieldIdChangedMeta,
647
727
  ): void {
648
- const { models, onUpdate, ...updatedPaths } = args;
728
+ const { models, previousPath, newPath, onUpdate } = args;
649
729
 
650
730
  for (const { model: slice } of models) {
651
- const updatedSliceModel = traverseSharedSlice({
731
+ const updateSlice = traverseSharedSlice({
652
732
  path: ["."],
653
733
  slice,
654
734
  onField: ({ field }) => {
655
- return updateFieldContentRelationships({ ...updatedPaths, field });
735
+ return updateFieldContentRelationships({
736
+ field,
737
+ previousPath,
738
+ newPath,
739
+ });
656
740
  },
657
741
  });
658
742
 
659
- if (!isEqualModel(slice, updatedSliceModel)) {
660
- onUpdate(updatedSliceModel);
743
+ if (!isEqualModel(slice, updateSlice)) {
744
+ onUpdate({ model: updateSlice, previousModel: slice });
661
745
  }
662
746
  }
663
747
  }
@@ -676,9 +760,3 @@ function shallowCloneIfObject<T>(value: T): T {
676
760
 
677
761
  return value;
678
762
  }
679
-
680
- function pushIfDefined<T>(array: T[], value: T | undefined) {
681
- if (value) {
682
- array.push(value);
683
- }
684
- }