@slicemachine/manager 0.24.15-beta.6 → 0.24.15-beta.8
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/dist/managers/customTypes/CustomTypesManager.cjs +213 -4
- package/dist/managers/customTypes/CustomTypesManager.cjs.map +1 -1
- package/dist/managers/customTypes/CustomTypesManager.d.ts +130 -11
- package/dist/managers/customTypes/CustomTypesManager.js +215 -6
- package/dist/managers/customTypes/CustomTypesManager.js.map +1 -1
- package/package.json +4 -4
- package/src/managers/customTypes/CustomTypesManager.ts +365 -6
@@ -2,7 +2,13 @@ import * as t from "io-ts";
|
|
2
2
|
import * as prismicCustomTypesClient from "@prismicio/custom-types-client";
|
3
3
|
import {
|
4
4
|
CustomType,
|
5
|
+
Group,
|
6
|
+
NestableWidget,
|
7
|
+
NestedGroup,
|
5
8
|
SharedSlice,
|
9
|
+
UID,
|
10
|
+
traverseCustomType,
|
11
|
+
traverseSharedSlice,
|
6
12
|
} from "@prismicio/types-internal/lib/customtypes";
|
7
13
|
import {
|
8
14
|
CallHookReturnType,
|
@@ -82,6 +88,25 @@ type CustomTypesMachineManagerDeleteCustomTypeReturnType = {
|
|
82
88
|
errors: (DecodeError | HookError)[];
|
83
89
|
};
|
84
90
|
|
91
|
+
type CustomTypesMachineManagerUpdateCustomTypeReturnType = {
|
92
|
+
errors: (DecodeError | HookError)[];
|
93
|
+
};
|
94
|
+
|
95
|
+
type CustomTypeFieldIdChangedMeta = {
|
96
|
+
previousPath: string[];
|
97
|
+
newPath: string[];
|
98
|
+
};
|
99
|
+
|
100
|
+
type CrCustomType =
|
101
|
+
| string
|
102
|
+
| { id: string; fields?: readonly CrCustomTypeNestedCr[] };
|
103
|
+
type CrCustomTypeNestedCr =
|
104
|
+
| string
|
105
|
+
| { id: string; customtypes: readonly CrCustomTypeFieldLeaf[] };
|
106
|
+
type CrCustomTypeFieldLeaf =
|
107
|
+
| string
|
108
|
+
| { id: string; fields?: readonly string[] };
|
109
|
+
|
85
110
|
export class CustomTypesManager extends BaseManager {
|
86
111
|
async readCustomTypeLibrary(): Promise<SliceMachineManagerReadCustomTypeLibraryReturnType> {
|
87
112
|
assertPluginsInitialized(this.sliceMachinePluginRunner);
|
@@ -167,19 +192,159 @@ export class CustomTypesManager extends BaseManager {
|
|
167
192
|
};
|
168
193
|
}
|
169
194
|
|
195
|
+
/**
|
196
|
+
* Update the Content Relationship API IDs for all existing custom types and
|
197
|
+
* slices. The change is determined by properties inside the `updateMeta`
|
198
|
+
* property.
|
199
|
+
*/
|
200
|
+
private async updateContentRelationships(
|
201
|
+
args: { model: CustomType } & CustomTypeFieldIdChangedMeta,
|
202
|
+
): Promise<
|
203
|
+
OnlyHookErrors<CallHookReturnType<CustomTypeUpdateHook>> & {
|
204
|
+
rollback?: () => Promise<void>;
|
205
|
+
}
|
206
|
+
> {
|
207
|
+
assertPluginsInitialized(this.sliceMachinePluginRunner);
|
208
|
+
|
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
|
+
});
|
245
|
+
|
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();
|
249
|
+
|
250
|
+
for (const library of libraries) {
|
251
|
+
const slices = await this.slices.readAllSlicesForLibrary({
|
252
|
+
libraryID: library.libraryID,
|
253
|
+
});
|
254
|
+
|
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
|
+
});
|
272
|
+
},
|
273
|
+
previousPath,
|
274
|
+
newPath,
|
275
|
+
});
|
276
|
+
}
|
277
|
+
|
278
|
+
// Process all the Content Relationship updates at once.
|
279
|
+
const crUpdatesResult = await Promise.all(
|
280
|
+
crUpdates.map((update) => update.updatePromise),
|
281
|
+
);
|
282
|
+
|
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
|
+
};
|
290
|
+
}
|
291
|
+
}
|
292
|
+
|
293
|
+
return { errors: [] };
|
294
|
+
}
|
295
|
+
|
170
296
|
async updateCustomType(
|
171
297
|
args: CustomTypeUpdateHookData,
|
172
|
-
): Promise<
|
298
|
+
): Promise<CustomTypesMachineManagerUpdateCustomTypeReturnType> {
|
173
299
|
assertPluginsInitialized(this.sliceMachinePluginRunner);
|
300
|
+
const { model } = args;
|
301
|
+
const { fieldIdChanged } = args.updateMeta ?? {};
|
174
302
|
|
175
|
-
|
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(
|
176
321
|
"custom-type:update",
|
177
|
-
|
322
|
+
{ model },
|
178
323
|
);
|
179
324
|
|
180
|
-
|
181
|
-
errors:
|
182
|
-
}
|
325
|
+
if (customTypeUpdateResult.errors.length > 0) {
|
326
|
+
return { errors: customTypeUpdateResult.errors };
|
327
|
+
}
|
328
|
+
|
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: [] };
|
183
348
|
}
|
184
349
|
|
185
350
|
async renameCustomType(
|
@@ -393,3 +558,197 @@ const InferSliceResponse = z.object({
|
|
393
558
|
}),
|
394
559
|
langSmithUrl: z.string().url().optional(),
|
395
560
|
});
|
561
|
+
|
562
|
+
function updateCRCustomType(
|
563
|
+
args: { customType: CrCustomType } & CustomTypeFieldIdChangedMeta,
|
564
|
+
): CrCustomType {
|
565
|
+
const [previousCustomTypeId, previousFieldId] = args.previousPath;
|
566
|
+
const [newCustomTypeId, newFieldId] = args.newPath;
|
567
|
+
|
568
|
+
if (!previousCustomTypeId || !newCustomTypeId) {
|
569
|
+
throw new Error(
|
570
|
+
"Could not find a customtype id in previousPath and/or newPath, which should not be possible.",
|
571
|
+
);
|
572
|
+
}
|
573
|
+
|
574
|
+
if (!previousFieldId || !newFieldId) {
|
575
|
+
throw new Error(
|
576
|
+
"Could not find a field id in previousPath and/or newPath, which should not be possible.",
|
577
|
+
);
|
578
|
+
}
|
579
|
+
|
580
|
+
const customType = shallowCloneIfObject(args.customType);
|
581
|
+
|
582
|
+
if (typeof customType === "string" || !customType.fields) {
|
583
|
+
return customType;
|
584
|
+
}
|
585
|
+
|
586
|
+
const matchedCustomTypeId = customType.id === previousCustomTypeId;
|
587
|
+
|
588
|
+
const newFields = customType.fields.map((fieldArg) => {
|
589
|
+
const customTypeField = shallowCloneIfObject(fieldArg);
|
590
|
+
|
591
|
+
if (typeof customTypeField === "string") {
|
592
|
+
if (
|
593
|
+
matchedCustomTypeId &&
|
594
|
+
customTypeField === previousFieldId &&
|
595
|
+
customTypeField !== newFieldId
|
596
|
+
) {
|
597
|
+
// We have reached a field id that matches the id that was renamed,
|
598
|
+
// so we update it new one. The field is a string, so return the new
|
599
|
+
// id.
|
600
|
+
return newFieldId;
|
601
|
+
}
|
602
|
+
|
603
|
+
return customTypeField;
|
604
|
+
}
|
605
|
+
|
606
|
+
if (
|
607
|
+
matchedCustomTypeId &&
|
608
|
+
customTypeField.id === previousFieldId &&
|
609
|
+
customTypeField.id !== newFieldId
|
610
|
+
) {
|
611
|
+
// We have reached a field id that matches the id that was renamed,
|
612
|
+
// so we update it new one.
|
613
|
+
// Since field is not a string, we don't exit, as we might have
|
614
|
+
// something to update further down in customtypes.
|
615
|
+
customTypeField.id = newFieldId;
|
616
|
+
}
|
617
|
+
|
618
|
+
return {
|
619
|
+
...customTypeField,
|
620
|
+
customtypes: customTypeField.customtypes.map((customTypeArg) => {
|
621
|
+
const nestedCustomType = shallowCloneIfObject(customTypeArg);
|
622
|
+
|
623
|
+
if (
|
624
|
+
typeof nestedCustomType === "string" ||
|
625
|
+
!nestedCustomType.fields ||
|
626
|
+
// Since we are on the last level, if we don't start matching right
|
627
|
+
// at the custom type id, we can return exit early because it's not
|
628
|
+
// a match.
|
629
|
+
nestedCustomType.id !== previousCustomTypeId
|
630
|
+
) {
|
631
|
+
return nestedCustomType;
|
632
|
+
}
|
633
|
+
|
634
|
+
return {
|
635
|
+
...nestedCustomType,
|
636
|
+
fields: nestedCustomType.fields.map((fieldArg) => {
|
637
|
+
const nestedCustomTypeField = shallowCloneIfObject(fieldArg);
|
638
|
+
|
639
|
+
if (
|
640
|
+
nestedCustomTypeField === previousFieldId &&
|
641
|
+
nestedCustomTypeField !== newFieldId
|
642
|
+
) {
|
643
|
+
// Matches the previous id, so we update it and return because
|
644
|
+
// it's the last level.
|
645
|
+
return newFieldId;
|
646
|
+
}
|
647
|
+
|
648
|
+
return nestedCustomTypeField;
|
649
|
+
}),
|
650
|
+
};
|
651
|
+
}),
|
652
|
+
};
|
653
|
+
});
|
654
|
+
|
655
|
+
return { ...customType, fields: newFields };
|
656
|
+
}
|
657
|
+
|
658
|
+
/**
|
659
|
+
* Update the Content Relationship API IDs of a single field. The change is
|
660
|
+
* determined by the `previousPath` and `newPath` properties.
|
661
|
+
*/
|
662
|
+
function updateFieldContentRelationships<
|
663
|
+
T extends UID | NestableWidget | Group | NestedGroup,
|
664
|
+
>(args: { field: T } & CustomTypeFieldIdChangedMeta): T {
|
665
|
+
const { field, ...updateMeta } = args;
|
666
|
+
if (
|
667
|
+
field.type !== "Link" ||
|
668
|
+
field.config?.select !== "document" ||
|
669
|
+
!field.config?.customtypes
|
670
|
+
) {
|
671
|
+
// not a content relationship field
|
672
|
+
return field;
|
673
|
+
}
|
674
|
+
|
675
|
+
const newCustomTypes = field.config.customtypes.map((customType) => {
|
676
|
+
return updateCRCustomType({ customType, ...updateMeta });
|
677
|
+
});
|
678
|
+
|
679
|
+
return {
|
680
|
+
...field,
|
681
|
+
config: { ...field.config, customtypes: newCustomTypes },
|
682
|
+
};
|
683
|
+
}
|
684
|
+
|
685
|
+
export function updateCustomTypeContentRelationships(
|
686
|
+
args: {
|
687
|
+
models: { model: CustomType }[];
|
688
|
+
onUpdate: (model: { previousModel: CustomType; model: CustomType }) => void;
|
689
|
+
} & CustomTypeFieldIdChangedMeta,
|
690
|
+
): void {
|
691
|
+
const { models, previousPath, newPath, onUpdate } = args;
|
692
|
+
|
693
|
+
for (const { model: customType } of models) {
|
694
|
+
const updatedCustomType = traverseCustomType({
|
695
|
+
customType,
|
696
|
+
onField: ({ field }) => {
|
697
|
+
return updateFieldContentRelationships({
|
698
|
+
field,
|
699
|
+
previousPath,
|
700
|
+
newPath,
|
701
|
+
});
|
702
|
+
},
|
703
|
+
});
|
704
|
+
|
705
|
+
if (!isEqualModel(customType, updatedCustomType)) {
|
706
|
+
onUpdate({ model: updatedCustomType, previousModel: customType });
|
707
|
+
}
|
708
|
+
}
|
709
|
+
}
|
710
|
+
|
711
|
+
export function updateSharedSliceContentRelationships(
|
712
|
+
args: {
|
713
|
+
models: { model: SharedSlice }[];
|
714
|
+
onUpdate: (model: {
|
715
|
+
previousModel: SharedSlice;
|
716
|
+
model: SharedSlice;
|
717
|
+
}) => void;
|
718
|
+
} & CustomTypeFieldIdChangedMeta,
|
719
|
+
): void {
|
720
|
+
const { models, previousPath, newPath, onUpdate } = args;
|
721
|
+
|
722
|
+
for (const { model: slice } of models) {
|
723
|
+
const updateSlice = traverseSharedSlice({
|
724
|
+
path: ["."],
|
725
|
+
slice,
|
726
|
+
onField: ({ field }) => {
|
727
|
+
return updateFieldContentRelationships({
|
728
|
+
field,
|
729
|
+
previousPath,
|
730
|
+
newPath,
|
731
|
+
});
|
732
|
+
},
|
733
|
+
});
|
734
|
+
|
735
|
+
if (!isEqualModel(slice, updateSlice)) {
|
736
|
+
onUpdate({ model: updateSlice, previousModel: slice });
|
737
|
+
}
|
738
|
+
}
|
739
|
+
}
|
740
|
+
|
741
|
+
function isEqualModel<T extends CustomType | SharedSlice>(
|
742
|
+
modelA: T,
|
743
|
+
modelB: T,
|
744
|
+
): boolean {
|
745
|
+
return JSON.stringify(modelA) === JSON.stringify(modelB);
|
746
|
+
}
|
747
|
+
|
748
|
+
function shallowCloneIfObject<T>(value: T): T {
|
749
|
+
if (typeof value === "object") {
|
750
|
+
return { ...value };
|
751
|
+
}
|
752
|
+
|
753
|
+
return value;
|
754
|
+
}
|