@prismicio/types-internal 2.2.0-traverse.alpha-12 → 2.2.0

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 (47) hide show
  1. package/lib/import/converters/Document.js +2 -0
  2. package/lib/import/converters/fields/Group.d.ts +4 -0
  3. package/lib/import/converters/fields/Group.js +14 -0
  4. package/lib/import/converters/fields/RepeatableZone.d.ts +317 -0
  5. package/lib/import/converters/fields/RepeatableZone.js +15 -0
  6. package/lib/import/converters/fields/RepeatableZoneItem.d.ts +11 -0
  7. package/lib/import/converters/fields/RepeatableZoneItem.js +19 -0
  8. package/lib/import/converters/fields/Slices/SharedSliceContent.js +6 -41
  9. package/lib/import/converters/fields/index.d.ts +1 -0
  10. package/lib/import/converters/fields/index.js +1 -0
  11. package/lib/import/validators/fields/ImportField.d.ts +11 -1
  12. package/lib/import/validators/fields/ImportField.js +6 -1
  13. package/lib/import/validators/fields/ImportGroup.d.ts +345 -0
  14. package/lib/import/validators/fields/ImportGroup.js +27 -0
  15. package/lib/import/validators/fields/ImportSlices/SharedSlice/SharedSlice.d.ts +5 -3
  16. package/lib/import/validators/fields/ImportSlices/SharedSlice/SharedSlice.js +4 -4
  17. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/index.d.ts +0 -1
  18. package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/index.js +0 -1
  19. package/lib/import/validators/fields/RepeatableZone.d.ts +11 -0
  20. package/lib/import/validators/fields/RepeatableZone.js +14 -0
  21. package/lib/import/validators/fields/RepeatableZoneItem.d.ts +11 -0
  22. package/lib/import/validators/fields/RepeatableZoneItem.js +39 -0
  23. package/lib/import/validators/fields/index.d.ts +2 -0
  24. package/lib/import/validators/fields/index.js +2 -0
  25. package/lib/import/validators/fields/nestable/Nestable.d.ts +60 -0
  26. package/lib/import/validators/fields/nestable/Nestable.js +32 -30
  27. package/package.json +1 -1
  28. package/src/import/converters/Document.ts +7 -1
  29. package/src/import/converters/fields/Group.ts +18 -0
  30. package/src/import/converters/fields/RepeatableZone.ts +19 -0
  31. package/src/import/converters/fields/RepeatableZoneItem.ts +32 -0
  32. package/src/import/converters/fields/Slices/SharedSliceContent.ts +9 -76
  33. package/src/import/converters/fields/index.ts +1 -0
  34. package/src/import/validators/fields/ImportField.ts +7 -2
  35. package/src/import/validators/fields/ImportGroup.ts +45 -0
  36. package/src/import/validators/fields/ImportSlices/SharedSlice/SharedSlice.ts +6 -18
  37. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/index.ts +0 -1
  38. package/src/import/validators/fields/RepeatableZone.ts +21 -0
  39. package/src/import/validators/fields/RepeatableZoneItem.ts +64 -0
  40. package/src/import/validators/fields/index.ts +2 -0
  41. package/src/import/validators/fields/nestable/Nestable.ts +34 -31
  42. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContent.ts +0 -64
  43. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContentEntry.ts +0 -100
  44. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/errors.ts +0 -10
  45. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/index.ts +0 -2
  46. package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/types.ts +0 -1
  47. package/src/utils/io-ts.ts +0 -29
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prismicio/types-internal",
3
- "version": "2.2.0-traverse.alpha-12",
3
+ "version": "2.2.0",
4
4
  "description": "Prismic types for Custom Types and Prismic Data",
5
5
  "keywords": [
6
6
  "typescript",
@@ -2,7 +2,11 @@ import type { Asset, Embed } from "../../common"
2
2
  import type { Document, WidgetContent } from "../../content"
3
3
  import type { ImportDocument } from "../validators"
4
4
  import type { ImportField } from "../validators/fields/ImportField"
5
- import { convertNestableWidget, importSlicesConverter } from "./fields"
5
+ import {
6
+ convertNestableWidget,
7
+ importGroupConverter,
8
+ importSlicesConverter,
9
+ } from "./fields"
6
10
 
7
11
  export function convertImportToContent(
8
12
  document: ImportDocument,
@@ -26,6 +30,8 @@ function convertWidget(
26
30
  switch (field.type) {
27
31
  case "Slices":
28
32
  return importSlicesConverter(field.value, assets, embeds)
33
+ case "Group":
34
+ return importGroupConverter(field.value, assets, embeds)
29
35
  default:
30
36
  return convertNestableWidget(field, assets, embeds)
31
37
  }
@@ -0,0 +1,18 @@
1
+ import type { Asset, Embed } from "../../../common"
2
+ import type { GroupContent } from "../../../content"
3
+ import { GroupContentType } from "../../../content"
4
+ import type { ImportGroup } from "../../validators/fields/ImportGroup"
5
+ import { repeatableZoneConverter } from "./RepeatableZone"
6
+
7
+ export const importGroupConverter = (
8
+ field: ImportGroup["value"],
9
+ assets: Record<Asset["id"], Asset | undefined>,
10
+ embeds: Record<string, Embed | undefined>,
11
+ ): GroupContent | undefined => {
12
+ if (field === null) return
13
+
14
+ return {
15
+ __TYPE__: GroupContentType,
16
+ value: repeatableZoneConverter(field, assets, embeds),
17
+ }
18
+ }
@@ -0,0 +1,19 @@
1
+ import type { Asset, Embed } from "../../../common"
2
+ import { GroupItemContentType, NestableContent } from "../../../content"
3
+ import type { RepeatableZone } from "../../validators"
4
+ import { repeatableZoneItemConverter } from "./RepeatableZoneItem"
5
+
6
+ export const repeatableZoneConverter = (
7
+ zone: RepeatableZone,
8
+ assets: Record<Asset["id"], Asset | undefined>,
9
+ embeds: Record<string, Embed | undefined>,
10
+ ) => {
11
+ return zone.map((item) => {
12
+ const groupContent: Record<string, NestableContent> =
13
+ repeatableZoneItemConverter(item, assets, embeds)
14
+ return {
15
+ __TYPE__: GroupItemContentType,
16
+ value: Object.entries(groupContent),
17
+ }
18
+ })
19
+ }
@@ -0,0 +1,32 @@
1
+ import { pipe } from "fp-ts/function"
2
+ import * as O from "fp-ts/Option"
3
+ import * as R from "fp-ts/Record"
4
+
5
+ import type { Asset, Embed } from "../../../common"
6
+ import type { NestableContent } from "../../../content"
7
+ import type { RepeatableZoneItem } from "../../validators"
8
+ import { convertNestableWidget } from "./nestable"
9
+
10
+ /**
11
+ * Converts the RepeatableZoneItem which is a record of widget keys and ImportNestable values to a record of widget keys and NestableContent values.
12
+ *
13
+ * @param content a single RepeatableZoneItem
14
+ * @param assets assets that are required for a conversion of a nestable widget
15
+ * @param embeds embeds that are required for a conversion of a nestable widget
16
+ */
17
+ export const repeatableZoneItemConverter = (
18
+ content: RepeatableZoneItem,
19
+ assets: Record<Asset["id"], Asset | undefined>,
20
+ embeds: Record<string, Embed | undefined>,
21
+ ): Record<string, NestableContent> =>
22
+ pipe(
23
+ content,
24
+ // convertNestableWidget can theoretically return undefined, so we need to filter out those values
25
+ R.filterMap((contentValue) =>
26
+ pipe(
27
+ contentValue,
28
+ (content) => content && convertNestableWidget(content, assets, embeds),
29
+ O.fromNullable,
30
+ ),
31
+ ),
32
+ )
@@ -1,78 +1,9 @@
1
- import { pipe } from "fp-ts/function"
2
- import * as O from "fp-ts/Option"
3
- import * as R from "fp-ts/Record"
4
-
5
1
  import type { Asset, Embed } from "../../../../common"
6
- import type { NestableContent, SharedSliceContent } from "../../../../content"
7
- import {
8
- GroupItemContentType,
9
- SharedSliceContentType,
10
- } from "../../../../content"
11
- import type {
12
- SharedSlice as ImportSharedSlice,
13
- SharedSliceContent as ImportSharedSliceContent,
14
- } from "../../../validators/fields/ImportSlices/SharedSlice"
15
- import { convertNestableWidget } from "../nestable"
16
-
17
- /**
18
- * Converts the ImportSharedSliceContent which is a record of widget keys and ImportNestable values to a record of widget keys and NestableContent values.
19
- *
20
- * @param content a single ImportSharedSliceContent
21
- * @param assets assets that are required for a conversion of a nestable widget
22
- * @param embeds embeds that are required for a conversion of a nestable widget
23
- */
24
- const sharedSliceContentConverter = (
25
- content: ImportSharedSliceContent,
26
- assets: Record<Asset["id"], Asset | undefined>,
27
- embeds: Record<string, Embed | undefined>,
28
- ): Record<string, NestableContent> =>
29
- pipe(
30
- content,
31
- // convertNestableWidget can theoretically return undefined, so we need to filter out those values
32
- R.filterMap((contentValue) =>
33
- pipe(
34
- contentValue,
35
- (content) => convertNestableWidget(content, assets, embeds),
36
- O.fromNullable,
37
- ),
38
- ),
39
- )
40
-
41
- /**
42
- * Converts a list of items to a list of repeatable widgets.
43
- * Each ImportSharedSliceContent element is a record of fields, but in the content model it is represented as a list of tuples (key, value)
44
- *
45
- * For example, given the following slice content:
46
- * {
47
- * "slice_text": (ImportNestable),
48
- * "slice_number": (ImportNestable),
49
- * }
50
- *
51
- * has to be converted to:
52
- *
53
- * {
54
- * __TYPE__: "GroupItemContentType",
55
- * value: [
56
- * ["slice_text", (NestableContent)],
57
- * ["slice_number", (NestableContent)],
58
- * ]
59
- * }
60
- *
61
- * @param items list of items to be converted
62
- * @param assets assets that are required for a conversion of a nestable widget
63
- */
64
- const itemsConverter = (
65
- items: ImportSharedSliceContent[],
66
- assets: Record<Asset["id"], Asset | undefined>,
67
- embeds: Record<string, Embed | undefined>,
68
- ): SharedSliceContent["items"] =>
69
- items.map((item) =>
70
- pipe(
71
- sharedSliceContentConverter(item, assets, embeds),
72
- (record) => Object.entries(record),
73
- (entries) => ({ __TYPE__: GroupItemContentType, value: entries }),
74
- ),
75
- )
2
+ import type { SharedSliceContent } from "../../../../content"
3
+ import { SharedSliceContentType } from "../../../../content"
4
+ import type { SharedSlice as ImportSharedSlice } from "../../../validators/fields/ImportSlices/SharedSlice"
5
+ import { repeatableZoneConverter } from "../RepeatableZone"
6
+ import { repeatableZoneItemConverter } from "../RepeatableZoneItem"
76
7
 
77
8
  /**
78
9
  * Builds SharedSliceContent model from ImportSharedSlice
@@ -87,8 +18,10 @@ export const importSharedSliceContentConverter = (
87
18
  ): SharedSliceContent => ({
88
19
  __TYPE__: SharedSliceContentType,
89
20
  primary: field.primary
90
- ? sharedSliceContentConverter(field.primary, assets, embeds)
21
+ ? repeatableZoneItemConverter(field.primary, assets, embeds)
91
22
  : {},
92
- items: field.items ? itemsConverter(field.items, assets, embeds) : [],
23
+ items: field.items
24
+ ? repeatableZoneConverter(field.items, assets, embeds)
25
+ : [],
93
26
  variation: field.variation,
94
27
  })
@@ -1,3 +1,4 @@
1
+ export * from "./Group"
1
2
  export * from "./nestable"
2
3
  export * from "./Slices"
3
4
  export * from "./UID"
@@ -1,8 +1,9 @@
1
1
  import type { StaticWidget } from "../../../customtypes"
2
+ import { ImportGroup } from "./ImportGroup"
2
3
  import { ImportSlices } from "./ImportSlices"
3
4
  import { ImportNestable } from "./nestable"
4
5
 
5
- export type ImportField = ImportSlices | ImportNestable
6
+ export type ImportField = ImportSlices | ImportNestable | ImportGroup
6
7
 
7
8
  export const ImportField = {
8
9
  is(u: unknown): u is ImportField {
@@ -20,8 +21,12 @@ export const ImportField = {
20
21
  codec: ImportSlices(field),
21
22
  result: ImportSlices(field).decode(content),
22
23
  }
23
- case "Choice":
24
24
  case "Group":
25
+ return {
26
+ codec: ImportGroup(field),
27
+ result: ImportGroup(field).decode(content),
28
+ }
29
+ case "Choice":
25
30
  throw new Error(`Unsupported type of field ${field.type}`)
26
31
  default:
27
32
  return ImportNestable.decode(field)(content)
@@ -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 type { Group } from "../../../customtypes"
6
+ import { EmptyArrayOrElse } from "../../../validators"
7
+ import { withCustomError } from "../../../validators/function"
8
+ import { ImportContent } from "./ImportContent"
9
+ import { RepeatableZone } from "./RepeatableZone"
10
+
11
+ export const ImportGroupValue = (groupCustomType?: Group) => {
12
+ const fieldsModel = groupCustomType?.config?.fields ?? {}
13
+ const groupArrayCodec = RepeatableZone(fieldsModel)
14
+
15
+ return withCustomError(
16
+ new t.Type<RepeatableZone>(
17
+ groupArrayCodec.name,
18
+ groupArrayCodec.is,
19
+ (u, c) =>
20
+ pipe(
21
+ groupArrayCodec.validate(u, c),
22
+ E.chain((groups) => {
23
+ if (
24
+ groupCustomType?.config?.repeat === false &&
25
+ groups.length > 1
26
+ ) {
27
+ return t.failure(
28
+ groups,
29
+ c,
30
+ "The custom type for this group field does not allow multiple group items",
31
+ )
32
+ }
33
+ return t.success(groups)
34
+ }),
35
+ ),
36
+ t.identity,
37
+ ),
38
+ () => "The group field value must be an array",
39
+ )
40
+ }
41
+
42
+ export const ImportGroup = (groupCustomType?: Group) =>
43
+ ImportContent("Group", EmptyArrayOrElse(ImportGroupValue(groupCustomType)))
44
+
45
+ export type ImportGroup = t.TypeOf<ReturnType<typeof ImportGroup>>
@@ -4,10 +4,10 @@ import * as t from "io-ts"
4
4
 
5
5
  import type { SharedSlice as SharedSliceCustomType } from "../../../../../customtypes"
6
6
  import { NonEmptyString, String } from "../../../../../validators"
7
- import { withCustomError } from "../../../../../validators/function"
7
+ import { RepeatableZone } from "../../RepeatableZone"
8
+ import { RepeatableZoneItem } from "../../RepeatableZoneItem"
8
9
  import {
9
10
  OptionalSharedSliceId,
10
- SharedSliceContent,
11
11
  SharedSliceId,
12
12
  SharedSliceType,
13
13
  SharedSliceVariation,
@@ -18,8 +18,8 @@ export type SharedSlice = {
18
18
  slice_type: string
19
19
  name: string
20
20
  variation: string
21
- primary: SharedSliceContent | null | undefined
22
- items: SharedSliceContent[] | null | undefined
21
+ primary: RepeatableZoneItem | null | undefined
22
+ items: RepeatableZone | null | undefined
23
23
  slice_label: string | null | undefined
24
24
  version: string | null | undefined
25
25
  }
@@ -70,22 +70,10 @@ export const SharedSlice = (sharedSlices: SharedSliceCustomType[]) =>
70
70
  // We validate the 'primary' and 'items' content fields, for which we need the Variation custom type retrieved in the previous step
71
71
  t
72
72
  .partial({
73
- primary: SharedSliceContent(
74
- decoded.slice_type.slice_type,
75
- "primary",
73
+ primary: RepeatableZoneItem(
76
74
  decoded.variation.data.primary ?? {},
77
75
  ),
78
- // We need to use this wrapper codec instead of using t.array(SharedSliceContent) directly in order to get proper custom error messages
79
- items: withCustomError(
80
- t.array(
81
- SharedSliceContent(
82
- decoded.slice_type.slice_type,
83
- "items",
84
- decoded.variation.data.items ?? {},
85
- ),
86
- ),
87
- () => "The value must be an array",
88
- ),
76
+ items: RepeatableZone(decoded.variation.data.items ?? {}),
89
77
  })
90
78
  .validate(u, c),
91
79
  E.map(({ primary, items }) => ({ ...decoded, primary, items })),
@@ -1,5 +1,4 @@
1
1
  export * from "./OptionalSharedSliceId"
2
- export * from "./SharedSliceContent"
3
2
  export * from "./SharedSliceId"
4
3
  export * from "./SharedSliceType"
5
4
  export * from "./SharedSliceVariation"
@@ -0,0 +1,21 @@
1
+ import * as t from "io-ts"
2
+
3
+ import type { WidgetKey } from "../../../common"
4
+ import type { NestableWidget } from "../../../customtypes"
5
+ import { withCustomError } from "../../../validators/function"
6
+ import { RepeatableZoneItem } from "./RepeatableZoneItem"
7
+
8
+ export type RepeatableZone = t.TypeOf<ReturnType<typeof RepeatableZone>>
9
+
10
+ /**
11
+ * A custom type for a repeatable zone. This type of structure can be found 'items' field of a slice or in the group field.
12
+ *
13
+ * @param fieldModels a record of all nestable widgets that can be used in the zone
14
+ */
15
+ export const RepeatableZone = (
16
+ fieldModels: Record<WidgetKey, NestableWidget>,
17
+ ) =>
18
+ withCustomError(
19
+ t.array(RepeatableZoneItem(fieldModels)),
20
+ () => "The repeatable zone value must be an array",
21
+ )
@@ -0,0 +1,64 @@
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 { withCustomError } from "../../../validators/function"
8
+ import { ImportNestable } from "./nestable"
9
+
10
+ export type RepeatableZoneItem = Record<WidgetKey, ImportNestable>
11
+
12
+ /**
13
+ * A custom type for an item of a repeatable zone. This type of structure can be found in the 'primary' and 'items' fields of a slice or in the group field.
14
+ *
15
+ * @param fieldModels a record of all nestable widgets that can be used in the zone
16
+ */
17
+ export const RepeatableZoneItem = (
18
+ fieldModels: Record<WidgetKey, NestableWidget>,
19
+ ) =>
20
+ new t.Type<RepeatableZoneItem>(
21
+ "RepeatableZoneItem",
22
+ (u): u is RepeatableZoneItem =>
23
+ t.record(WidgetKey, t.unknown).is(u) &&
24
+ Object.values(u).reduce<boolean>(
25
+ (acc, value) => acc && ImportNestable.is(value),
26
+ true,
27
+ ),
28
+ (u, c) => {
29
+ const codecEntries = Object.entries(fieldModels).map(
30
+ ([key, model]) => [key, ImportNestable.getCodec(model)] as const,
31
+ )
32
+
33
+ const groupCodec = withCustomError(
34
+ t.partial({
35
+ ...Object.fromEntries(codecEntries),
36
+ }),
37
+ () => "The value must be an object",
38
+ )
39
+
40
+ return pipe(
41
+ groupCodec.validate(u, c),
42
+ E.chain((decodedContent) => {
43
+ // Validate if all fields are present in the model
44
+ const keys = Object.keys(decodedContent)
45
+ const errors: t.Errors = keys.flatMap((key) =>
46
+ fieldModels[key]
47
+ ? []
48
+ : [
49
+ {
50
+ value: decodedContent[key],
51
+ context: c,
52
+ message: `The field '${key}' is not defined in the custom type`,
53
+ },
54
+ ],
55
+ )
56
+
57
+ return errors.length > 0
58
+ ? t.failures(errors)
59
+ : t.success(decodedContent as RepeatableZoneItem) // We can never have a key with decoded value 'undefined' so we can ignore the type derived from t.partial
60
+ }),
61
+ )
62
+ },
63
+ t.identity,
64
+ )
@@ -1,2 +1,4 @@
1
1
  export * from "./nestable"
2
+ export * from "./RepeatableZone"
3
+ export * from "./RepeatableZoneItem"
2
4
  export * from "./UID"
@@ -26,6 +26,37 @@ export type ImportNestable =
26
26
  | ImportImage
27
27
  | ImportRichText
28
28
 
29
+ const getCodecOrThrow = (field: NestableWidget) => {
30
+ switch (field.type) {
31
+ case "Boolean":
32
+ return ImportBoolean
33
+ case "Color":
34
+ return ImportColor
35
+ case "Number":
36
+ return ImportNumber(field)
37
+ case "Select":
38
+ return ImportSelect(field)
39
+ case "Text":
40
+ return ImportText
41
+ case "Date":
42
+ return ImportDate
43
+ case "Timestamp":
44
+ return ImportTimestamp
45
+ case "Embed":
46
+ return ImportEmbed
47
+ case "Link":
48
+ return ImportLink
49
+ case "Image":
50
+ return ImportImage(field)
51
+ case "GeoPoint":
52
+ return ImportGeoPoint
53
+ case "StructuredText":
54
+ return ImportRichText(field)
55
+ default:
56
+ throw new Error(`Unsupported type of nestable field ${field.type}`)
57
+ }
58
+ }
59
+
29
60
  export const ImportNestable = {
30
61
  is(u: unknown): u is ImportNestable {
31
62
  return (
@@ -45,41 +76,13 @@ export const ImportNestable = {
45
76
  },
46
77
  decode: (field: NestableWidget) => {
47
78
  return (content: unknown) => {
48
- const codec = (() => {
49
- switch (field.type) {
50
- case "Boolean":
51
- return ImportBoolean
52
- case "Color":
53
- return ImportColor
54
- case "Number":
55
- return ImportNumber(field)
56
- case "Select":
57
- return ImportSelect(field)
58
- case "Text":
59
- return ImportText
60
- case "Date":
61
- return ImportDate
62
- case "Timestamp":
63
- return ImportTimestamp
64
- case "Embed":
65
- return ImportEmbed
66
- case "Link":
67
- return ImportLink
68
- case "Image":
69
- return ImportImage(field)
70
- case "GeoPoint":
71
- return ImportGeoPoint
72
- case "StructuredText":
73
- return ImportRichText(field)
74
- default:
75
- throw new Error(`Unsupported type of nestable field ${field.type}`)
76
- }
77
- })()
78
-
79
+ const codec = getCodecOrThrow(field)
79
80
  return {
80
81
  codec,
81
82
  result: codec.decode(content),
82
83
  }
83
84
  }
84
85
  },
86
+
87
+ getCodec: getCodecOrThrow,
85
88
  }
@@ -1,64 +0,0 @@
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 { withCustomError } from "../../../../../../../validators/function"
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
- withCustomError(
37
- t.UnknownRecord,
38
- () => "The value must be an object",
39
- ).validate(u, c),
40
- E.chain((rawContent) =>
41
- pipe(
42
- // We split the object into entries because there is no easy other way to decode each value with a different codec
43
- Object.entries(rawContent).map((entry) =>
44
- SharedSliceContentEntry(
45
- sliceName,
46
- sliceContentField,
47
- sliceFieldModels,
48
- ).validate(entry, c),
49
- ),
50
- // We get a validation result for each entry in the content object (t.Validation<SharedSliceContentEntry>[]),
51
- // however we want to get a single validation result for the whole array of entries (t.Validation<SharedSliceContentEntry[]>).
52
- combineValidationResults,
53
- // We build the object back from entries if all decoded successfully
54
- E.map((entries) =>
55
- entries.reduce(
56
- (acc, [key, value]) => ({ ...acc, [key]: value }),
57
- {},
58
- ),
59
- ),
60
- ),
61
- ),
62
- ),
63
- t.identity,
64
- )
@@ -1,100 +0,0 @@
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
- { key, actual: content, type: codec },
87
- ...error.context.slice(1), // We ignore the first context element with "" key coming from ImportNestable decoder
88
- ]
89
-
90
- const updatedError: t.ValidationError = { ...error, context }
91
-
92
- return updatedError
93
- }),
94
- ),
95
- )
96
- }),
97
- )
98
- },
99
- t.identity,
100
- )