@prismicio/types-internal 2.2.0-alpha.10 → 2.2.0-alpha.11

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.
Files changed (136) hide show
  1. package/lib/common/UUID.d.ts +7 -0
  2. package/lib/common/UUID.js +8 -0
  3. package/lib/content/fields/slices/Slice/RepeatableContent.js +0 -1
  4. package/lib/import/converters/Document.d.ts +1 -2
  5. package/lib/import/converters/Document.js +2 -0
  6. package/lib/import/converters/fields/Slices/SharedSlice.d.ts +4 -0
  7. package/lib/import/converters/fields/Slices/SharedSlice.js +19 -0
  8. package/lib/import/converters/fields/Slices/SharedSliceContent.d.ts +10 -0
  9. package/lib/import/converters/fields/Slices/SharedSliceContent.js +58 -0
  10. package/lib/import/converters/fields/Slices/SliceItem.d.ts +4 -0
  11. package/lib/import/converters/fields/Slices/SliceItem.js +24 -0
  12. package/lib/import/converters/fields/Slices/Slices.d.ts +4 -0
  13. package/lib/import/converters/fields/Slices/Slices.js +16 -0
  14. package/lib/import/converters/fields/Slices/index.d.ts +1 -0
  15. package/lib/import/converters/fields/Slices/index.js +4 -0
  16. package/lib/import/converters/fields/index.d.ts +1 -0
  17. package/lib/import/converters/fields/index.js +1 -0
  18. package/lib/import/converters/fields/nestable/Nestable.d.ts +2 -2
  19. package/lib/import/validators/fields/ImportField.d.ts +11 -1
  20. package/lib/import/validators/fields/ImportField.js +6 -1
  21. package/lib/import/validators/fields/ImportSlices/ImportSliceId.d.ts +9 -0
  22. package/lib/import/validators/fields/ImportSlices/ImportSliceId.js +43 -0
  23. package/lib/import/validators/fields/ImportSlices/ImportSliceItem.d.ts +24 -0
  24. package/lib/import/validators/fields/ImportSlices/ImportSliceItem.js +87 -0
  25. package/lib/import/validators/fields/ImportSlices/ImportSliceItemContent.d.ts +12 -0
  26. package/lib/import/validators/fields/ImportSlices/ImportSliceItemContent.js +41 -0
  27. package/lib/import/validators/fields/ImportSlices/ImportSlices.d.ts +8 -0
  28. package/lib/import/validators/fields/ImportSlices/ImportSlices.js +29 -0
  29. package/lib/import/validators/fields/ImportSlices/SharedSlice/SharedSlice.d.ts +14 -0
  30. package/lib/import/validators/fields/ImportSlices/SharedSlice/SharedSlice.js +60 -0
  31. package/lib/import/validators/fields/ImportSlices/SharedSlice/errors.d.ts +4 -0
  32. package/lib/import/validators/fields/ImportSlices/SharedSlice/errors.js +7 -0
  33. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/OptionalSharedSliceId.d.ts +2 -0
  34. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/OptionalSharedSliceId.js +15 -0
  35. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContent.d.ts +17 -0
  36. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContent.js +30 -0
  37. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContentEntry.d.ts +43 -0
  38. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContentEntry.js +69 -0
  39. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/errors.d.ts +4 -0
  40. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/errors.js +6 -0
  41. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/index.d.ts +2 -0
  42. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/index.js +5 -0
  43. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/types.d.ts +1 -0
  44. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/types.js +2 -0
  45. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceId.d.ts +9 -0
  46. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceId.js +39 -0
  47. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceType.d.ts +11 -0
  48. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceType.js +24 -0
  49. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceVariation.d.ts +11 -0
  50. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceVariation.js +24 -0
  51. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/index.d.ts +5 -0
  52. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/index.js +8 -0
  53. package/lib/import/validators/fields/ImportSlices/SharedSlice/index.d.ts +3 -0
  54. package/lib/import/validators/fields/ImportSlices/SharedSlice/index.js +6 -0
  55. package/lib/import/validators/fields/ImportSlices/SharedSlice/utils.d.ts +339 -0
  56. package/lib/import/validators/fields/ImportSlices/SharedSlice/utils.js +11 -0
  57. package/lib/import/validators/fields/ImportSlices/index.d.ts +1 -0
  58. package/lib/import/validators/fields/ImportSlices/index.js +4 -0
  59. package/lib/import/validators/fields/ImportSlices/utils.d.ts +4 -0
  60. package/lib/import/validators/fields/ImportSlices/utils.js +35 -0
  61. package/lib/import/validators/fields/Slices/SharedSlice/SharedSlice.d.ts +14 -0
  62. package/lib/import/validators/fields/Slices/SharedSlice/SharedSlice.js +60 -0
  63. package/lib/import/validators/fields/Slices/SharedSlice/errors.d.ts +5 -0
  64. package/lib/import/validators/fields/Slices/SharedSlice/errors.js +8 -0
  65. package/lib/import/validators/fields/Slices/SharedSlice/fields/OptionalSharedSliceId.d.ts +2 -0
  66. package/lib/import/validators/fields/Slices/SharedSlice/fields/OptionalSharedSliceId.js +15 -0
  67. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceContent/SharedSliceContent.d.ts +17 -0
  68. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceContent/SharedSliceContent.js +38 -0
  69. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceContent/SharedSliceContentEntry.d.ts +43 -0
  70. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceContent/SharedSliceContentEntry.js +69 -0
  71. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceContent/errors.d.ts +5 -0
  72. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceContent/errors.js +9 -0
  73. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceContent/index.d.ts +2 -0
  74. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceContent/index.js +5 -0
  75. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceContent/types.d.ts +1 -0
  76. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceContent/types.js +2 -0
  77. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceId.d.ts +9 -0
  78. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceId.js +39 -0
  79. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceItems.d.ts +11 -0
  80. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceItems.js +25 -0
  81. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceType.d.ts +11 -0
  82. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceType.js +24 -0
  83. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceVariation.d.ts +11 -0
  84. package/lib/import/validators/fields/Slices/SharedSlice/fields/SharedSliceVariation.js +24 -0
  85. package/lib/import/validators/fields/Slices/SharedSlice/fields/index.d.ts +6 -0
  86. package/lib/import/validators/fields/Slices/SharedSlice/fields/index.js +9 -0
  87. package/lib/import/validators/fields/Slices/SharedSlice/index.d.ts +3 -0
  88. package/lib/import/validators/fields/Slices/SharedSlice/index.js +6 -0
  89. package/lib/import/validators/fields/Slices/SharedSlice/utils.d.ts +339 -0
  90. package/lib/import/validators/fields/Slices/SharedSlice/utils.js +11 -0
  91. package/lib/import/validators/fields/Slices/Slices.d.ts +8 -0
  92. package/lib/import/validators/fields/Slices/Slices.js +29 -0
  93. package/lib/import/validators/fields/Slices/index.d.ts +1 -0
  94. package/lib/import/validators/fields/Slices/index.js +4 -0
  95. package/lib/import/validators/fields/Slices/utils.d.ts +4 -0
  96. package/lib/import/validators/fields/Slices/utils.js +35 -0
  97. package/lib/import/validators/fields/Slices.d.ts +4 -0
  98. package/lib/import/validators/fields/Slices.js +12 -0
  99. package/lib/import/validators/fields/nestable/Image.d.ts +30 -47
  100. package/lib/import/validators/fields/nestable/Image.js +22 -53
  101. package/lib/utils/io-ts.d.ts +2 -0
  102. package/lib/utils/io-ts.js +22 -0
  103. package/lib/validators/BasicTypes.d.ts +1 -0
  104. package/lib/validators/BasicTypes.js +2 -1
  105. package/lib/validators/NonEmptyString.js +2 -1
  106. package/package.json +4 -2
  107. package/src/common/UUID.ts +18 -0
  108. package/src/content/fields/slices/Slice/RepeatableContent.ts +0 -1
  109. package/src/import/converters/Document.ts +8 -3
  110. package/src/import/converters/fields/Slices/SharedSlice.ts +24 -0
  111. package/src/import/converters/fields/Slices/SharedSliceContent.ts +94 -0
  112. package/src/import/converters/fields/Slices/Slices.ts +20 -0
  113. package/src/import/converters/fields/Slices/index.ts +1 -0
  114. package/src/import/converters/fields/index.ts +1 -0
  115. package/src/import/converters/fields/nestable/Nestable.ts +2 -2
  116. package/src/import/validators/fields/ImportField.ts +7 -2
  117. package/src/import/validators/fields/ImportSlices/ImportSlices.ts +54 -0
  118. package/src/import/validators/fields/ImportSlices/SharedSlice/SharedSlice.ts +114 -0
  119. package/src/import/validators/fields/ImportSlices/SharedSlice/errors.ts +6 -0
  120. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/OptionalSharedSliceId.ts +20 -0
  121. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContent.ts +61 -0
  122. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContentEntry.ts +100 -0
  123. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/errors.ts +10 -0
  124. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/index.ts +2 -0
  125. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/types.ts +1 -0
  126. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceId.ts +65 -0
  127. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceType.ts +45 -0
  128. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceVariation.ts +45 -0
  129. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/index.ts +5 -0
  130. package/src/import/validators/fields/ImportSlices/SharedSlice/index.ts +3 -0
  131. package/src/import/validators/fields/ImportSlices/SharedSlice/utils.ts +21 -0
  132. package/src/import/validators/fields/ImportSlices/index.ts +1 -0
  133. package/src/import/validators/fields/ImportSlices/utils.ts +43 -0
  134. package/src/utils/io-ts.ts +29 -0
  135. package/src/validators/BasicTypes.ts +5 -0
  136. package/src/validators/NonEmptyString.ts +4 -4
@@ -0,0 +1,54 @@
1
+ import * as E from "fp-ts/Either"
2
+ import { pipe } from "fp-ts/function"
3
+ import * as t from "io-ts"
4
+
5
+ import type { StaticSlices } from "../../../../customtypes"
6
+ import { EmptyObjectOrElse } from "../../../../validators"
7
+ import { ImportContent } from "../ImportContent"
8
+ import { SharedSlice } from "./SharedSlice"
9
+ import {
10
+ extractSupportedSlices,
11
+ findImportSharedSliceDuplicateIds,
12
+ } from "./utils"
13
+
14
+ export const ImportSlices = (staticSlices: StaticSlices) => {
15
+ const supportedSlices = extractSupportedSlices(staticSlices)
16
+
17
+ // For now we only support the SharedSlice, however if we want to support more in the future
18
+ // we would have to change the codec here to something like this: t.array(t.union([ImportSharedSlice(sharedSlices), NewSliceCodec(newSliceCustomTypes)])).
19
+ const SlicesArrayCodec = t.array(SharedSlice(supportedSlices))
20
+
21
+ return ImportContent(
22
+ "Slices",
23
+ EmptyObjectOrElse(
24
+ new t.Type<SharedSlice[]>(
25
+ "ImportSlices",
26
+ (u): u is SharedSlice[] => SlicesArrayCodec.is(u),
27
+ (u, c) => {
28
+ return pipe(
29
+ SlicesArrayCodec.validate(u, c),
30
+ E.chain((slices) => {
31
+ // This part might not make sense for all Slice types in the future, but for now we only support the SharedSlice
32
+ // In case we support more in the future, we would have to filter only the relevant type for this check
33
+ const sharedSliceDuplicates =
34
+ findImportSharedSliceDuplicateIds(slices)
35
+ if (sharedSliceDuplicates.length > 0) {
36
+ return t.failure(
37
+ slices,
38
+ [],
39
+ `Duplicate slice IDs detected: ${sharedSliceDuplicates.join(
40
+ ", ",
41
+ )}`,
42
+ )
43
+ }
44
+ return t.success(slices)
45
+ }),
46
+ )
47
+ },
48
+ t.identity,
49
+ ),
50
+ ),
51
+ )
52
+ }
53
+
54
+ export type ImportSlices = t.TypeOf<ReturnType<typeof ImportSlices>>
@@ -0,0 +1,114 @@
1
+ import * as E from "fp-ts/Either"
2
+ import { pipe } from "fp-ts/function"
3
+ import * as t from "io-ts"
4
+
5
+ import type { SharedSlice as SharedSliceCustomType } from "../../../../../customtypes"
6
+ import { AnyArray, NonEmptyString, String } from "../../../../../validators"
7
+ import {
8
+ OptionalSharedSliceId,
9
+ SharedSliceContent,
10
+ SharedSliceId,
11
+ SharedSliceType,
12
+ SharedSliceVariation,
13
+ } from "./fields"
14
+
15
+ export type SharedSlice = {
16
+ id: SharedSliceId | null | undefined
17
+ slice_type: string
18
+ name: string
19
+ variation: string
20
+ primary: SharedSliceContent | null | undefined
21
+ items: SharedSliceContent[] | null | undefined
22
+ slice_label: string | null | undefined
23
+ version: string | null | undefined
24
+ }
25
+
26
+ const SharedSliceShape = t.type({
27
+ id: OptionalSharedSliceId,
28
+ slice_type: NonEmptyString,
29
+ name: String,
30
+ variation: NonEmptyString,
31
+ primary: t.union([t.undefined, t.null, t.record(NonEmptyString, t.unknown)]),
32
+ items: t.union([
33
+ t.undefined,
34
+ t.null,
35
+ t.array(t.record(NonEmptyString, t.unknown)),
36
+ ]),
37
+ slice_label: t.union([t.undefined, t.null, String]),
38
+ version: t.union([t.undefined, t.null, String]),
39
+ })
40
+
41
+ export const SharedSlice = (sharedSlices: SharedSliceCustomType[]) =>
42
+ new t.Type<SharedSlice>(
43
+ "SharedSlice",
44
+ (u): u is SharedSlice => SharedSliceShape.is(u),
45
+ (u, c) =>
46
+ pipe(
47
+ // We validate the base fields that we can validate directly based on the provided 'sharedSlices'
48
+ t
49
+ .type({
50
+ id: OptionalSharedSliceId,
51
+ slice_type: SharedSliceType(sharedSlices),
52
+ slice_label: t.union([t.undefined, t.null, String]),
53
+ version: t.union([t.undefined, t.null, String]),
54
+ })
55
+ .validate(u, c),
56
+ E.chain((decoded) =>
57
+ pipe(
58
+ // We validate the 'variation' field, for which we need the SharedSlice custom type retrieved in the first step
59
+ t
60
+ .type({
61
+ variation: SharedSliceVariation(decoded.slice_type.data),
62
+ })
63
+ .validate(u, c),
64
+ E.map(({ variation }) => ({ ...decoded, variation })),
65
+ ),
66
+ ),
67
+ E.chain((decoded) =>
68
+ pipe(
69
+ // We validate the 'primary' and 'items' content fields, for which we need the Variation custom type retrieved in the previous step
70
+ t
71
+ .partial({
72
+ primary: SharedSliceContent(
73
+ decoded.slice_type.slice_type,
74
+ "primary",
75
+ decoded.variation.data.primary ?? {},
76
+ ),
77
+ // We need to use this wrapper codec instead of using t.array(SharedSliceContent) directly in order to get proper custom error messages
78
+ items: AnyArray.pipe(
79
+ t.array(
80
+ SharedSliceContent(
81
+ decoded.slice_type.slice_type,
82
+ "items",
83
+ decoded.variation.data.items ?? {},
84
+ ),
85
+ ),
86
+ ),
87
+ })
88
+ .validate(u, c),
89
+ E.map(({ primary, items }) => ({ ...decoded, primary, items })),
90
+ ),
91
+ ),
92
+ E.map(
93
+ ({
94
+ id,
95
+ slice_type,
96
+ variation,
97
+ primary,
98
+ items,
99
+ slice_label,
100
+ version,
101
+ }) => ({
102
+ id,
103
+ slice_type: slice_type.slice_type,
104
+ name: slice_type.data.name,
105
+ variation: variation.variation,
106
+ primary,
107
+ items,
108
+ slice_label,
109
+ version: version ?? variation.data.version,
110
+ }),
111
+ ),
112
+ ),
113
+ t.identity,
114
+ )
@@ -0,0 +1,6 @@
1
+ export const SharedSliceErrors = {
2
+ SliceTypeNotFound: (slice_type: string) =>
3
+ `Shared slice '${slice_type}' not found in document's custom type`,
4
+ VariationNotFound: (variation: string, slice_type: string) =>
5
+ `Variation '${variation}' not found for Slice '${slice_type}'`,
6
+ } as const
@@ -0,0 +1,20 @@
1
+ import * as t from "io-ts"
2
+
3
+ import { SharedSliceId } from "./SharedSliceId"
4
+
5
+ // Wrapping the 'SharedSliceId' codec in an optional codec here for the sake of error reporting.
6
+ // When using t.union([t.undefined, t.null, SharedSliceId]) we get a nested error context
7
+ // with the actual decoding error at position 2 in the context array.
8
+ // To avoid mutating the error context we create this new wrapper codec so that the error context will always have desired depth.
9
+ export const OptionalSharedSliceId = new t.Type<
10
+ SharedSliceId | null | undefined
11
+ >(
12
+ "OptionalSharedSliceId",
13
+ (u): u is SharedSliceId | null | undefined =>
14
+ u === null || u === undefined || SharedSliceId.is(u),
15
+ (u, c) => {
16
+ if (u === null || u === undefined) return t.success(u)
17
+ return SharedSliceId.validate(u, c)
18
+ },
19
+ t.identity,
20
+ )
@@ -0,0 +1,61 @@
1
+ import * as E from "fp-ts/Either"
2
+ import { pipe } from "fp-ts/lib/function"
3
+ import * as t from "io-ts"
4
+
5
+ import { WidgetKey } from "../../../../../../../common"
6
+ import type { NestableWidget } from "../../../../../../../customtypes"
7
+ import { combineValidationResults } from "../../../../../../../utils/io-ts"
8
+ import { AnyObject } from "../../../../../../../validators"
9
+ import type { ImportNestable } from "../../../../nestable"
10
+ import { SharedSliceContentEntry } from "./SharedSliceContentEntry"
11
+
12
+ /**
13
+ * Represents the object in the 'primary' field or a single item in the 'items' array in the SharedSlice.
14
+ */
15
+ export type SharedSliceContent = Record<WidgetKey, ImportNestable>
16
+
17
+ /**
18
+ * Builds the decoder for a single entry in the 'primary' field or a single item in the 'items' array in the SharedSlice.
19
+ *
20
+ * @param sliceName name of the slice - used for error reporting in order to know which slice is being decoded
21
+ * @param sliceContentField 'primary' or 'items' - used for error reporting in order to know which field of the slice is being decoded
22
+ * @param sliceFieldModels models of the fields of the slice from custom type - used to know which codec to use for decoding each value of the object
23
+ * @constructor
24
+ */
25
+ export const SharedSliceContent = (
26
+ sliceName: string,
27
+ sliceContentField: "primary" | "items",
28
+ sliceFieldModels: Record<WidgetKey, NestableWidget>,
29
+ ) =>
30
+ new t.Type<SharedSliceContent, SharedSliceContent, unknown>(
31
+ "SharedSliceContent",
32
+ (u): u is SharedSliceContent => t.record(WidgetKey, t.unknown).is(u),
33
+ (u, c) =>
34
+ pipe(
35
+ // We first decode the raw content object in the shape of Record<WidgetKey, unknown>
36
+ AnyObject.validate(u, c),
37
+ E.chain((rawContent) =>
38
+ pipe(
39
+ // We split the object into entries because there is no easy other way to decode each value with a different codec
40
+ Object.entries(rawContent).map((entry) =>
41
+ SharedSliceContentEntry(
42
+ sliceName,
43
+ sliceContentField,
44
+ sliceFieldModels,
45
+ ).validate(entry, c),
46
+ ),
47
+ // We get a validation result for each entry in the content object (t.Validation<SharedSliceContentEntry>[]),
48
+ // however we want to get a single validation result for the whole array of entries (t.Validation<SharedSliceContentEntry[]>).
49
+ combineValidationResults,
50
+ // We build the object back from entries if all decoded successfully
51
+ E.map((entries) =>
52
+ entries.reduce(
53
+ (acc, [key, value]) => ({ ...acc, [key]: value }),
54
+ {},
55
+ ),
56
+ ),
57
+ ),
58
+ ),
59
+ ),
60
+ t.identity,
61
+ )
@@ -0,0 +1,100 @@
1
+ import * as E from "fp-ts/Either"
2
+ import { pipe } from "fp-ts/function"
3
+ import * as t from "io-ts"
4
+
5
+ import { WidgetKey } from "../../../../../../../common"
6
+ import type { NestableWidget } from "../../../../../../../customtypes"
7
+ import { ImportNestable } from "../../../../nestable"
8
+ import { SharedSliceContentErrors } from "./errors"
9
+
10
+ export type SharedSliceContentEntry = [WidgetKey, ImportNestable]
11
+ /**
12
+ * Represents a single entry in the 'primary' field or a single entry in an item in the 'items' array in the SharedSlice. It is used to build the SharedSliceContent type from entries of the import object.
13
+ *
14
+ * For example, given the following slice:
15
+ * {
16
+ * "slice_type": "my_slice",
17
+ * "variation": "default",
18
+ * "primary": {
19
+ * "slice_text": "abc", // This is one entry
20
+ * "slice_text2": "def: // This is another entry
21
+ * },
22
+ * "items": [
23
+ * {
24
+ * "slice_number1": 0, // This is one entry
25
+ * "slice_number2": 1 // And this is yet another entry
26
+ * }
27
+ * ]
28
+ * }
29
+ *
30
+ * The 'primary' object is converted to following entries to be decoded:
31
+ * [
32
+ * ["slice_text", "abc"],
33
+ * ["slice_text2", "def"]
34
+ * ]
35
+ * and the first item in the 'items' array is converted to following entries to be decoded:
36
+ * [
37
+ * ["slice_number1", 0],
38
+ * ["slice_number2", 1]
39
+ * ]
40
+ *
41
+ * We decode them as separate entries because each of the values might require a different codec as per what is specified in the SharedSlice custom type.
42
+ * In this way we save on the decoding performance in comparison to a scenario where we would decode the whole content object as a record with a union type decoder for each value.
43
+ *
44
+ * @param sliceName name of the slice - used for error reporting in order to know which slice is being decoded
45
+ * @param sliceContentField 'primary' or 'items' - used for error reporting in order to know which field of the slice is being decoded
46
+ * @param sliceFieldModels models of the fields of the slice from custom type - used to know which codec to use for decoding each value of the object
47
+ */
48
+ export const SharedSliceContentEntry = (
49
+ sliceName: string,
50
+ sliceContentField: "primary" | "items",
51
+ sliceFieldModels: Record<WidgetKey, NestableWidget>,
52
+ ) =>
53
+ new t.Type<SharedSliceContentEntry, SharedSliceContentEntry, unknown>(
54
+ "SharedSliceContentEntry",
55
+ (u): u is SharedSliceContentEntry =>
56
+ t.tuple([WidgetKey, t.unknown]).is(u) && ImportNestable.is(u[1]),
57
+ (u, c) => {
58
+ return pipe(
59
+ t.tuple([WidgetKey, t.unknown]).validate(u, c),
60
+ E.chain(([key, content]) => {
61
+ const model: NestableWidget | undefined = sliceFieldModels[key]
62
+ if (!model) {
63
+ return t.failure(
64
+ [key, content],
65
+ [...c, { key, actual: content, type: t.unknown }],
66
+ SharedSliceContentErrors.UnknownField(
67
+ sliceName,
68
+ sliceContentField,
69
+ key,
70
+ ),
71
+ )
72
+ }
73
+ const { codec, result } = ImportNestable.decode(model)(content)
74
+
75
+ return pipe(
76
+ result,
77
+ E.map<ImportNestable, SharedSliceContentEntry>((decodedContent) => [
78
+ key,
79
+ decodedContent,
80
+ ]),
81
+ E.mapLeft(
82
+ (errors): t.Errors =>
83
+ errors.map((error) => {
84
+ const context = [
85
+ ...c,
86
+ ...error.context.filter(({ key }) => !!key), // We replace the "" key coming from ImportNestable decoder
87
+ { key, actual: content, type: codec },
88
+ ]
89
+
90
+ const updatedError: t.ValidationError = { ...error, context }
91
+
92
+ return updatedError
93
+ }),
94
+ ),
95
+ )
96
+ }),
97
+ )
98
+ },
99
+ t.identity,
100
+ )
@@ -0,0 +1,10 @@
1
+ import type { SharedSliceContentField } from "./types"
2
+
3
+ export const SharedSliceContentErrors = {
4
+ UnknownField: (
5
+ sliceName: string,
6
+ contentField: SharedSliceContentField,
7
+ fieldName: string,
8
+ ) =>
9
+ `Unsupported field '${fieldName}'for '${contentField}' content in Shared Slice '${sliceName}'`,
10
+ } as const
@@ -0,0 +1,2 @@
1
+ export * from "./errors"
2
+ export * from "./SharedSliceContent"
@@ -0,0 +1 @@
1
+ export type SharedSliceContentField = "primary" | "items"
@@ -0,0 +1,65 @@
1
+ import * as E from "fp-ts/Either"
2
+ import { pipe } from "fp-ts/function"
3
+ import * as t from "io-ts"
4
+
5
+ import { UUID } from "../../../../../../common/UUID"
6
+ import { NonEmptyString } from "../../../../../../validators"
7
+
8
+ export type SharedSliceId = `${string}$${UUID}`
9
+
10
+ const baseErrorText =
11
+ "Slice id must be a non empty string matching the following pattern 'slice_type$uuid'"
12
+
13
+ export const SharedSliceIdValidationError = {
14
+ Base: baseErrorText,
15
+ IncorrectUUID: (uuid: string | undefined) =>
16
+ `Incorrect UUID: '${uuid}' - ${baseErrorText}`,
17
+ IncorrectSliceType: (sliceType: string | undefined) =>
18
+ `Incorrect slice_type: '${sliceType}' - ${baseErrorText}`,
19
+ } as const
20
+
21
+ export const SharedSliceId = new t.Type<SharedSliceId, string, unknown>(
22
+ "SharedSliceId",
23
+ (u): u is SharedSliceId => {
24
+ if (typeof u !== "string") {
25
+ return false
26
+ }
27
+ const [sliceName, uuid] = u.split("$")
28
+ return NonEmptyString.is(sliceName) && UUID.is(uuid)
29
+ },
30
+ (u, c) =>
31
+ pipe(
32
+ NonEmptyString.validate(u, c),
33
+ E.mapLeft((errors) =>
34
+ errors.map(
35
+ (error): t.ValidationError => ({
36
+ ...error,
37
+ message: SharedSliceIdValidationError.Base,
38
+ }),
39
+ ),
40
+ ),
41
+ E.chain((sliceId) => {
42
+ const [sliceName, uuid] = sliceId.split("$")
43
+ return pipe(
44
+ t.tuple([NonEmptyString, UUID]).validate([sliceName, uuid], c),
45
+ E.mapLeft((errors) =>
46
+ errors.map((error): t.ValidationError => {
47
+ const tupleErrorContext = error.context[error.context.length - 1]
48
+ return {
49
+ ...error,
50
+ context: [
51
+ ...error.context.slice(0, -1), // We don't want the tuple decoder error context
52
+ ],
53
+ message:
54
+ tupleErrorContext?.key === "0"
55
+ ? SharedSliceIdValidationError.IncorrectSliceType(sliceName)
56
+ : SharedSliceIdValidationError.IncorrectUUID(uuid),
57
+ }
58
+ }),
59
+ ),
60
+ )
61
+ }),
62
+ E.map(([sliceName, uuid]) => `${sliceName}$${uuid}`),
63
+ ),
64
+ (sliceId) => sliceId,
65
+ )
@@ -0,0 +1,45 @@
1
+ import * as E from "fp-ts/Either"
2
+ import { pipe } from "fp-ts/function"
3
+ import * as t from "io-ts"
4
+
5
+ import { SharedSlice as SharedSliceCustomType } from "../../../../../../customtypes"
6
+ import { NonEmptyString } from "../../../../../../validators"
7
+ import { findSlice } from "../utils"
8
+
9
+ export type SharedSliceType = {
10
+ slice_type: string
11
+ data: SharedSliceCustomType
12
+ }
13
+
14
+ /**
15
+ * slice_type - the validated slice_type
16
+ * slice - SharedSlice custom type data matching the slice_type. We return it alongside the validated slice_type, because it is needed to decode the 'variation' field in the SharedSlice (see SharedSliceVariation.ts)
17
+ */
18
+ const SharedSliceTypeShape = t.type({
19
+ slice_type: NonEmptyString,
20
+ slice: SharedSliceCustomType,
21
+ })
22
+
23
+ /**
24
+ * Builds the decoder for the 'slice_type' field in the SharedSlice. Alongside the decoded 'slice_type' it also returns the SharedSlice data matching the slice_type.
25
+ * @param slices all the SharedSlices from the custom type
26
+ */
27
+ export const SharedSliceType = (slices: SharedSliceCustomType[]) =>
28
+ new t.Type<SharedSliceType, string, unknown>(
29
+ "slice_type",
30
+ (u): u is SharedSliceType => SharedSliceTypeShape.is(u),
31
+ (u, c) =>
32
+ pipe(
33
+ NonEmptyString.validate(u, c),
34
+ E.chain((slice_type) =>
35
+ pipe(
36
+ findSlice(slices)(slice_type),
37
+ E.fold(
38
+ (err) => t.failure(u, c, err),
39
+ (data) => t.success({ slice_type, data }),
40
+ ),
41
+ ),
42
+ ),
43
+ ),
44
+ ({ slice_type }) => slice_type,
45
+ )
@@ -0,0 +1,45 @@
1
+ import * as E from "fp-ts/Either"
2
+ import { pipe } from "fp-ts/function"
3
+ import * as t from "io-ts"
4
+
5
+ import { SharedSlice, Variation } from "../../../../../../customtypes"
6
+ import { NonEmptyString } from "../../../../../../validators"
7
+ import { findSliceVariation } from "../utils"
8
+
9
+ export type SharedSliceVariation = {
10
+ variation: string
11
+ data: Variation
12
+ }
13
+
14
+ /**
15
+ * variation - the validated variation of the slice
16
+ * data - Variation data matching the variation. We return it alongside the validated variation, because it is needed to decode the 'primary' and 'items' fields in the SharedSlice.
17
+ */
18
+ const SharedSliceVariationShape = t.type({
19
+ variation: NonEmptyString,
20
+ data: Variation,
21
+ })
22
+
23
+ /**
24
+ * Builds the decoder for the 'variation' field in the SharedSlice. Alongside the decoded 'variation' it also returns the Variation data matching the variation custom type.
25
+ * @param slice SharedSlice from the custom type
26
+ */
27
+ export const SharedSliceVariation = (slice: SharedSlice) =>
28
+ new t.Type<SharedSliceVariation, string, unknown>(
29
+ "variation",
30
+ (u): u is SharedSliceVariation => SharedSliceVariationShape.is(u),
31
+ (u, c) =>
32
+ pipe(
33
+ NonEmptyString.validate(u, c),
34
+ E.chain((variation) =>
35
+ pipe(
36
+ findSliceVariation(slice)(variation),
37
+ E.fold(
38
+ (err) => t.failure(u, c, err),
39
+ (data) => t.success({ variation, data }),
40
+ ),
41
+ ),
42
+ ),
43
+ ),
44
+ ({ variation }) => variation,
45
+ )
@@ -0,0 +1,5 @@
1
+ export * from "./OptionalSharedSliceId"
2
+ export * from "./SharedSliceContent"
3
+ export * from "./SharedSliceId"
4
+ export * from "./SharedSliceType"
5
+ export * from "./SharedSliceVariation"
@@ -0,0 +1,3 @@
1
+ export * from "./errors"
2
+ export * from "./fields"
3
+ export * from "./SharedSlice"
@@ -0,0 +1,21 @@
1
+ import * as E from "fp-ts/Either"
2
+ import { pipe } from "fp-ts/function"
3
+
4
+ import type { SharedSlice as SharedSliceCustomType } from "../../../../../customtypes"
5
+ import type { Variation } from "../../../../../customtypes"
6
+ import { SharedSliceErrors } from "./errors"
7
+
8
+ export const findSlice =
9
+ (slices: SharedSliceCustomType[]) => (sliceId: string) =>
10
+ pipe(
11
+ slices.find((slice) => slice.id === sliceId),
12
+ E.fromNullable(SharedSliceErrors.SliceTypeNotFound(sliceId)),
13
+ )
14
+
15
+ export const findSliceVariation =
16
+ (slice: SharedSliceCustomType) =>
17
+ (variation: string): E.Either<string, Variation> =>
18
+ pipe(
19
+ slice.variations.find((v) => v.id === variation),
20
+ E.fromNullable(SharedSliceErrors.VariationNotFound(variation, slice.id)),
21
+ )
@@ -0,0 +1 @@
1
+ export * from "./ImportSlices"
@@ -0,0 +1,43 @@
1
+ import * as A from "fp-ts/Array"
2
+ import { pipe } from "fp-ts/function"
3
+ import * as O from "fp-ts/Option"
4
+
5
+ import {
6
+ SharedSlice as SharedSliceCustomType,
7
+ StaticSlices,
8
+ } from "../../../../customtypes"
9
+ import type { SharedSlice, SharedSliceId } from "./SharedSlice"
10
+
11
+ // Extracts all supported slices from the static slices object
12
+ // For now we only support the SharedSlice, if we want to support other slices in the future, we have to add them here
13
+ export const extractSupportedSlices = (
14
+ staticSlices: StaticSlices,
15
+ ): SharedSliceCustomType[] =>
16
+ pipe(
17
+ Object.values(staticSlices.config?.choices ?? {}).map((slice) =>
18
+ pipe(SharedSliceCustomType.decode(slice), O.fromEither),
19
+ ),
20
+ A.compact,
21
+ )
22
+
23
+ export const findImportSharedSliceDuplicateIds = (
24
+ slices: SharedSlice[],
25
+ ): SharedSliceId[] => {
26
+ const duplicatesMap = slices.reduce((acc, { id }) => {
27
+ if (id) {
28
+ const currentNumOfDuplicates = acc[id] ?? 0
29
+ return {
30
+ ...acc,
31
+ [id]: acc[id] === undefined ? 0 : currentNumOfDuplicates + 1,
32
+ }
33
+ }
34
+ return acc
35
+ }, {} as Record<SharedSliceId, number>)
36
+
37
+ return Object.entries(duplicatesMap).flatMap(([id, numOfDuplicates]) => {
38
+ if (numOfDuplicates > 0) {
39
+ return [id]
40
+ }
41
+ return []
42
+ })
43
+ }
@@ -0,0 +1,29 @@
1
+ import * as E from "fp-ts/Either"
2
+ import { pipe } from "fp-ts/function"
3
+ import * as RA from "fp-ts/ReadonlyArray"
4
+ import type { Semigroup } from "fp-ts/Semigroup"
5
+ import type { Errors, Validation } from "io-ts"
6
+
7
+ /*
8
+ * Semigroup simply defines how to concatenate two values of the same type.
9
+ * In this instance, Errors from io-ts are arrays already, so we just need to concatenate them.
10
+ */
11
+ const validationErrorsSemigroup: Semigroup<Errors> = {
12
+ concat: (a: Errors, b: Errors) => [...a, ...b],
13
+ }
14
+
15
+ /*
16
+ * This is a helper function to combine multiple io-ts validations into one.
17
+ * t.array(...) works in the same way - it collects all the errors when decoding an array of values.
18
+ * However, it requires you to pass in a single io-ts validator as a parameter, which is not always convenient.
19
+ * Sometimes we have arrays of validation results received from function calls that don't have a validator instance.
20
+ */
21
+ export const combineValidationResults = <T>(
22
+ validationResults: readonly Validation<T>[],
23
+ ): Validation<readonly T[]> =>
24
+ pipe(
25
+ validationResults,
26
+ RA.traverse(E.getApplicativeValidation(validationErrorsSemigroup))(
27
+ (validation) => validation,
28
+ ),
29
+ )
@@ -51,3 +51,8 @@ export const AnyObject = withMessage(
51
51
  t.record(t.string, t.unknown),
52
52
  () => "The value must be an object",
53
53
  )
54
+
55
+ export const AnyArray = withMessage(
56
+ t.array(t.unknown),
57
+ () => "The value must be an array",
58
+ )
@@ -1,9 +1,9 @@
1
1
  import * as t from "io-ts"
2
+ import { withMessage } from "io-ts-types"
2
3
 
3
4
  import { refineType } from "./function"
4
5
 
5
- export default refineType(
6
- t.string,
7
- "nonEmptyString",
8
- (s) => s.trim().length > 0,
6
+ export default withMessage(
7
+ refineType(t.string, "nonEmptyString", (s) => s.trim().length > 0),
8
+ () => "The value must be a non-empty string",
9
9
  )