@slicemachine/manager 0.24.14-alpha.jp-update-cr-links-unit.4 → 0.24.14-alpha.jp-update-cr-links-only-when-changed.1

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.
@@ -188,6 +188,154 @@ export class CustomTypesManager extends BaseManager {
188
188
  };
189
189
  }
190
190
 
191
+ private updateCRCustomType(
192
+ args: { customType: CrCustomType } & CustomTypeFieldIdChangedMeta,
193
+ ): { customType: CrCustomType; changed: boolean } {
194
+ const { previousPath, newPath } = args;
195
+ const customType = shallowClone(args.customType);
196
+
197
+ const [previousId] = previousPath;
198
+ const [newId] = newPath;
199
+
200
+ if (!previousId || !newId || typeof customType === "string") {
201
+ return { customType, changed: false };
202
+ }
203
+
204
+ let modelHasChanged = false;
205
+
206
+ if (customType.fields) {
207
+ const newFields = customType.fields.map((fieldArg) => {
208
+ const field = shallowClone(fieldArg);
209
+
210
+ const previousId = previousPath[1];
211
+ const newId = newPath[1];
212
+
213
+ if (!previousId || !newId) {
214
+ return field;
215
+ }
216
+
217
+ if (typeof field === "string") {
218
+ if (field === previousId && field !== newId) {
219
+ modelHasChanged = true;
220
+
221
+ // We have reached a field id that matches the id that was renamed,
222
+ // so we update it new one. The field is a string, so return the new
223
+ // id.
224
+ return newId;
225
+ }
226
+
227
+ return field;
228
+ }
229
+
230
+ if (field.id === previousId && field.id !== newId) {
231
+ modelHasChanged = true;
232
+
233
+ // We have reached a field id that matches the id that was renamed,
234
+ // so we update it new one.
235
+ // Since field is not a string, we don't exit, as we might have
236
+ // something to update further down in customtypes.
237
+ field.id = newId;
238
+ }
239
+
240
+ return {
241
+ ...field,
242
+ customtypes: field.customtypes.map((customTypeArg) => {
243
+ const customType = shallowClone(customTypeArg);
244
+ const previousId = previousPath[2];
245
+ const newId = newPath[2];
246
+
247
+ if (!previousId || !newId) {
248
+ return customType;
249
+ }
250
+
251
+ if (typeof customType === "string") {
252
+ if (customType === previousId && customType !== newId) {
253
+ modelHasChanged = true;
254
+
255
+ // Matches the previous id, so we update it.
256
+ return newId;
257
+ }
258
+
259
+ return customType;
260
+ }
261
+
262
+ if (customType.id === previousId && customType.id !== newId) {
263
+ modelHasChanged = true;
264
+
265
+ // Matches the previous id, so we update it and return because
266
+ // it's the last level.
267
+ return { ...customType, id: newId };
268
+ }
269
+
270
+ return customType;
271
+ }),
272
+ };
273
+ });
274
+
275
+ return {
276
+ customType: { ...customType, fields: newFields },
277
+ changed: modelHasChanged,
278
+ };
279
+ }
280
+
281
+ return { customType, changed: modelHasChanged };
282
+ }
283
+
284
+ /**
285
+ * Map over the custom types of a Content Relationship Link and update the API
286
+ * IDs that were changed during the custom type update.
287
+ */
288
+ private updateCRCustomTypes(
289
+ args: { customTypes: CrCustomTypes } & CustomTypeFieldIdChangedMeta,
290
+ ): { customTypes: CrCustomTypes; changed: boolean } {
291
+ const { customTypes, ...updateMeta } = args;
292
+
293
+ let customTypeHasChanged = false;
294
+
295
+ const newCustomTypes = customTypes.map((customType) => {
296
+ const update = this.updateCRCustomType({ customType, ...updateMeta });
297
+
298
+ customTypeHasChanged ||= update.changed;
299
+
300
+ return update.customType;
301
+ });
302
+
303
+ return { customTypes: newCustomTypes, changed: customTypeHasChanged };
304
+ }
305
+
306
+ /**
307
+ * Update the Content Relationship API IDs of a single field. The change is
308
+ * determined by the `previousPath` and `newPath` properties.
309
+ */
310
+ private updateFieldContentRelationships<
311
+ T extends UID | NestableWidget | Group | NestedGroup,
312
+ >(
313
+ args: { field: T } & CustomTypeFieldIdChangedMeta,
314
+ ): { field: T; changed: boolean } {
315
+ const { field, ...updateMeta } = args;
316
+ if (
317
+ field.type !== "Link" ||
318
+ field.config?.select !== "document" ||
319
+ !field.config?.customtypes
320
+ ) {
321
+ // not a content relationship field
322
+ return { field, changed: false };
323
+ }
324
+
325
+ const update = this.updateCRCustomTypes({
326
+ ...updateMeta,
327
+ customTypes: field.config.customtypes,
328
+ });
329
+
330
+ return {
331
+ field: {
332
+ ...field,
333
+ config: { ...field.config, customtypes: update.customTypes },
334
+ },
335
+ changed: update.changed,
336
+ };
337
+ }
338
+
191
339
  /**
192
340
  * Update the Content Relationship API IDs for all existing custom types and
193
341
  * slices. The change is determined by properties inside the `updateMeta`
@@ -213,19 +361,34 @@ export class CustomTypesManager extends BaseManager {
213
361
  // any custom type and update them to use the new one.
214
362
  const customTypes = await this.readAllCustomTypes();
215
363
 
216
- updateCustomTypeContentRelationships({
217
- models: customTypes.models,
218
- onUpdate: (model) => {
219
- pushIfDefined(
220
- crUpdates,
221
- this.sliceMachinePluginRunner?.callHook("custom-type:update", {
222
- model,
364
+ for (const customType of customTypes.models) {
365
+ // Keep track of whether the model has changed to avoid calling the
366
+ // update hook if nothing has changed
367
+ let customTypeHasChanged = false;
368
+
369
+ const updatedCustomTypeModel = traverseCustomType({
370
+ customType: customType.model,
371
+ onField: ({ field }) => {
372
+ const update = this.updateFieldContentRelationships({
373
+ field,
374
+ previousPath,
375
+ newPath,
376
+ });
377
+
378
+ customTypeHasChanged ||= update.changed;
379
+
380
+ return update.field;
381
+ },
382
+ });
383
+
384
+ if (customTypeHasChanged) {
385
+ crUpdates.push(
386
+ this.sliceMachinePluginRunner.callHook("custom-type:update", {
387
+ model: updatedCustomTypeModel,
223
388
  }),
224
389
  );
225
- },
226
- previousPath,
227
- newPath,
228
- });
390
+ }
391
+ }
229
392
 
230
393
  // Find existing slice with content relationships that link to the renamed
231
394
  // field id in all libraries and update them to use the new one.
@@ -236,29 +399,45 @@ export class CustomTypesManager extends BaseManager {
236
399
  libraryID: library.libraryID,
237
400
  });
238
401
 
239
- updateSharedSliceContentRelationships({
240
- models: slices.models,
241
- onUpdate: (model) => {
242
- pushIfDefined(
243
- crUpdates,
244
- this.sliceMachinePluginRunner?.callHook("slice:update", {
402
+ for (const slice of slices.models) {
403
+ // Keep track of whether the model has changed to avoid calling the
404
+ // update hook if nothing has changed
405
+ let sliceHasChanged = false;
406
+
407
+ const updatedSliceModel = traverseSharedSlice({
408
+ path: ["."],
409
+ slice: slice.model,
410
+ onField: ({ field }) => {
411
+ const update = this.updateFieldContentRelationships({
412
+ field,
413
+ previousPath,
414
+ newPath,
415
+ });
416
+
417
+ sliceHasChanged ||= update.changed;
418
+
419
+ return update.field;
420
+ },
421
+ });
422
+
423
+ if (sliceHasChanged) {
424
+ crUpdates.push(
425
+ this.sliceMachinePluginRunner.callHook("slice:update", {
245
426
  libraryID: library.libraryID,
246
- model,
427
+ model: updatedSliceModel,
247
428
  }),
248
429
  );
249
- },
250
- previousPath,
251
- newPath,
252
- });
253
- }
430
+ }
431
+ }
254
432
 
255
- // Process all the Content Relationship updates at once.
256
- const crUpdatesResult = await Promise.all(crUpdates);
433
+ // Process all the Content Relationship updates at once.
434
+ const crUpdatesResult = await Promise.all(crUpdates);
257
435
 
258
- if (crUpdatesResult.some((result) => result.errors.length > 0)) {
259
- return {
260
- errors: crUpdatesResult.flatMap((result) => result.errors),
261
- };
436
+ if (crUpdatesResult.some((result) => result.errors.length > 0)) {
437
+ return {
438
+ errors: crUpdatesResult.flatMap((result) => result.errors),
439
+ };
440
+ }
262
441
  }
263
442
  }
264
443
  }
@@ -495,178 +674,10 @@ const InferSliceResponse = z.object({
495
674
  langSmithUrl: z.string().url().optional(),
496
675
  });
497
676
 
498
- function updateCRCustomType<T extends CrCustomType | CrCustomTypeFieldLeaf>(
499
- args: {
500
- customType: T;
501
- } & CustomTypeFieldIdChangedMeta,
502
- ): T {
503
- const { previousPath, newPath } = args;
504
-
505
- const customType = shallowCloneIfObject(args.customType);
506
-
507
- const [previousId] = previousPath;
508
- const [newId] = newPath;
509
-
510
- if (!previousId || !newId || typeof customType === "string") {
511
- return customType;
512
- }
513
-
514
- if (customType.fields) {
515
- const newFields = customType.fields.map((fieldArg) => {
516
- const field = shallowCloneIfObject(fieldArg);
517
-
518
- const previousId = previousPath[1];
519
- const newId = newPath[1];
520
-
521
- if (!previousId || !newId) {
522
- return field;
523
- }
524
-
525
- if (typeof field === "string") {
526
- if (field === previousId && field !== newId) {
527
- // We have reached a field id that matches the id that was renamed,
528
- // so we update it new one. The field is a string, so return the new
529
- // id.
530
- return newId;
531
- }
532
-
533
- return field;
534
- }
535
-
536
- if (field.id === previousId && field.id !== newId) {
537
- // We have reached a field id that matches the id that was renamed,
538
- // so we update it new one.
539
- // Since field is not a string, we don't exit, as we might have
540
- // something to update further down in customtypes.
541
- field.id = newId;
542
- }
543
-
544
- return {
545
- ...field,
546
- customtypes: field.customtypes.map((customType) => {
547
- return updateCRCustomType({
548
- customType,
549
- previousPath,
550
- newPath,
551
- });
552
- }),
553
- };
554
- });
555
-
556
- // @ts-expect-error We know that at this level we are returning the
557
- // right properties, but TypeScript will not trust it because it might
558
- // also have customtypes. This is because the type is not fully
559
- // recursive, it just has two levels of depth.
560
- return {
561
- id: customType.id,
562
- fields: newFields,
563
- };
564
- }
565
-
566
- return customType;
567
- }
568
-
569
- /**
570
- * Map over the custom types of a Content Relationship Link and update the API
571
- * IDs that were changed during the custom type update.
572
- */
573
- function updateCRCustomTypes(
574
- args: { customTypes: CrCustomTypes } & CustomTypeFieldIdChangedMeta,
575
- ): CrCustomTypes {
576
- const { customTypes, ...updateMeta } = args;
577
-
578
- return customTypes.map((customType) => {
579
- return updateCRCustomType({ customType, ...updateMeta });
580
- });
581
- }
582
-
583
- /**
584
- * Update the Content Relationship API IDs of a single field. The change is
585
- * determined by the `previousPath` and `newPath` properties.
586
- */
587
- function updateFieldContentRelationships<
588
- T extends UID | NestableWidget | Group | NestedGroup,
589
- >(args: { field: T } & CustomTypeFieldIdChangedMeta): T {
590
- const { field, ...updateMeta } = args;
591
- if (
592
- field.type !== "Link" ||
593
- field.config?.select !== "document" ||
594
- !field.config?.customtypes
595
- ) {
596
- // not a content relationship field
597
- return field;
598
- }
599
-
600
- const newCustomTypes = updateCRCustomTypes({
601
- ...updateMeta,
602
- customTypes: field.config.customtypes,
603
- });
604
-
605
- return {
606
- ...field,
607
- config: { ...field.config, customtypes: newCustomTypes },
608
- };
609
- }
610
-
611
- export function updateCustomTypeContentRelationships(
612
- args: {
613
- models: { model: CustomType }[];
614
- onUpdate: (model: CustomType) => void;
615
- } & CustomTypeFieldIdChangedMeta,
616
- ): void {
617
- const { models, previousPath, newPath, onUpdate } = args;
618
-
619
- for (const customType of models) {
620
- const updatedCustomTypeModel = traverseCustomType({
621
- customType: customType.model,
622
- onField: ({ field }) => {
623
- return updateFieldContentRelationships({
624
- field,
625
- previousPath,
626
- newPath,
627
- });
628
- },
629
- });
630
-
631
- onUpdate(updatedCustomTypeModel);
632
- }
633
- }
634
-
635
- export function updateSharedSliceContentRelationships(
636
- args: {
637
- models: { model: SharedSlice }[];
638
- onUpdate: (model: SharedSlice) => void;
639
- } & CustomTypeFieldIdChangedMeta,
640
- ): void {
641
- const { models, previousPath, newPath, onUpdate } = args;
642
-
643
- for (const slice of models) {
644
- const updatedSliceModel = traverseSharedSlice({
645
- path: ["."],
646
- slice: slice.model,
647
- onField: ({ field }) => {
648
- return updateFieldContentRelationships({
649
- field,
650
- previousPath,
651
- newPath,
652
- });
653
- },
654
- });
655
-
656
- onUpdate(updatedSliceModel);
657
- }
658
- }
659
-
660
- function shallowCloneIfObject<T>(value: T): T {
677
+ function shallowClone<T>(value: T): T {
661
678
  if (typeof value === "object") {
662
679
  return { ...value };
663
680
  }
664
681
 
665
682
  return value;
666
683
  }
667
-
668
- function pushIfDefined<T>(array: T[], value: T | undefined) {
669
- if (value) {
670
- array.push(value);
671
- }
672
- }