@prismicio/types-internal 2.2.0-alpha.2 → 2.2.0-alpha.4

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 (64) hide show
  1. package/lib/common/Asset.d.ts +15 -0
  2. package/lib/common/Asset.js +2 -0
  3. package/lib/common/index.d.ts +1 -0
  4. package/lib/common/index.js +1 -0
  5. package/lib/content/Document.d.ts +40 -40
  6. package/lib/content/fields/GroupContent.d.ts +12 -12
  7. package/lib/content/fields/WidgetContent.d.ts +56 -56
  8. package/lib/content/fields/nestable/NestableContent.d.ts +7 -7
  9. package/lib/content/fields/nestable/RichTextContent/Blocks.d.ts +12 -12
  10. package/lib/content/fields/nestable/RichTextContent/index.d.ts +9 -9
  11. package/lib/content/fields/slices/Slice/CompositeSliceContent.d.ts +14 -14
  12. package/lib/content/fields/slices/Slice/RepeatableContent.d.ts +5 -5
  13. package/lib/content/fields/slices/Slice/SharedSliceContent.d.ts +14 -14
  14. package/lib/content/fields/slices/Slice/SimpleSliceContent.d.ts +14 -14
  15. package/lib/content/fields/slices/Slice/index.d.ts +30 -30
  16. package/lib/content/fields/slices/SliceItem.d.ts +30 -30
  17. package/lib/content/fields/slices/SlicesContent.d.ts +42 -42
  18. package/lib/import/converters/Document.d.ts +2 -2
  19. package/lib/import/converters/Document.js +4 -4
  20. package/lib/import/converters/fields/nestable/GeooPoint.d.ts +3 -0
  21. package/lib/import/converters/fields/nestable/GeooPoint.js +15 -0
  22. package/lib/import/converters/fields/nestable/Image.d.ts +2 -16
  23. package/lib/import/converters/fields/nestable/Image.js +11 -11
  24. package/lib/import/converters/fields/nestable/Link.d.ts +1 -1
  25. package/lib/import/converters/fields/nestable/Link.js +23 -11
  26. package/lib/import/converters/fields/nestable/Nestable.d.ts +2 -2
  27. package/lib/import/converters/fields/nestable/Nestable.js +6 -2
  28. package/lib/import/converters/fields/nestable/index.d.ts +2 -0
  29. package/lib/import/converters/fields/nestable/index.js +2 -0
  30. package/lib/import/validators/fields/ImportField.d.ts +135 -6
  31. package/lib/import/validators/fields/nestable/GeoPoint.d.ts +13 -0
  32. package/lib/import/validators/fields/nestable/GeoPoint.js +13 -0
  33. package/lib/import/validators/fields/nestable/Image.d.ts +24 -3
  34. package/lib/import/validators/fields/nestable/Link.js +11 -6
  35. package/lib/import/validators/fields/nestable/Nestable.d.ts +139 -8
  36. package/lib/import/validators/fields/nestable/Nestable.js +13 -5
  37. package/lib/import/validators/fields/nestable/index.d.ts +2 -0
  38. package/lib/import/validators/fields/nestable/index.js +2 -0
  39. package/lib/utils/DocumentId.d.ts +1 -0
  40. package/lib/utils/DocumentId.js +7 -0
  41. package/lib/validators/NumberRange.d.ts +32 -0
  42. package/lib/validators/NumberRange.js +40 -0
  43. package/lib/validators/function.d.ts +20 -0
  44. package/lib/validators/function.js +41 -1
  45. package/lib/validators/index.d.ts +1 -0
  46. package/lib/validators/index.js +3 -1
  47. package/package.json +1 -1
  48. package/src/common/Asset.ts +15 -0
  49. package/src/common/index.ts +1 -0
  50. package/src/import/converters/Document.ts +6 -5
  51. package/src/import/converters/fields/nestable/GeooPoint.ts +16 -0
  52. package/src/import/converters/fields/nestable/Image.ts +7 -22
  53. package/src/import/converters/fields/nestable/Link.ts +33 -0
  54. package/src/import/converters/fields/nestable/Nestable.ts +9 -3
  55. package/src/import/converters/fields/nestable/index.ts +2 -0
  56. package/src/import/validators/fields/nestable/GeoPoint.ts +21 -0
  57. package/src/import/validators/fields/nestable/Image.ts +3 -16
  58. package/src/import/validators/fields/nestable/Link.ts +51 -0
  59. package/src/import/validators/fields/nestable/Nestable.ts +13 -3
  60. package/src/import/validators/fields/nestable/index.ts +2 -0
  61. package/src/utils/DocumentId.ts +9 -0
  62. package/src/validators/NumberRange.ts +51 -0
  63. package/src/validators/function.ts +44 -0
  64. package/src/validators/index.ts +1 -0
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addType = exports.isEmpty = exports.objectToMap = exports.grouped = exports.formatDate = exports.formatDateTime = exports.filterDouble = exports.refineType = exports.nullable = void 0;
3
+ exports.withFallbackMessage = exports.addType = exports.isEmpty = exports.objectToMap = exports.grouped = exports.formatDate = exports.formatDateTime = exports.filterDouble = exports.refineType = exports.nullable = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const fp_ts_1 = require("fp-ts");
6
6
  const function_1 = require("fp-ts/function");
7
7
  const t = (0, tslib_1.__importStar)(require("io-ts"));
8
+ const io_ts_types_1 = require("io-ts-types");
8
9
  const mapOutput_1 = require("io-ts-types/mapOutput");
9
10
  function nullable(c) {
10
11
  return t.union([c, t.null, t.undefined]);
@@ -58,3 +59,42 @@ function addType(codec, t) {
58
59
  return (0, mapOutput_1.mapOutput)(codec, (o) => ({ ...o, __TYPE__: t }));
59
60
  }
60
61
  exports.addType = addType;
62
+ /**
63
+ * Returns a clone of the given codec that tries to find sub-error with message already set.
64
+ * If there is such error it just returns sub-errors array.
65
+ * If there is no such error it generates new error with given message.
66
+ *
67
+ * @example
68
+ * expect(
69
+ * withFallbackMessage(
70
+ * t.type({age: withMessage(t.number, () => 'Invalid child')}),
71
+ * () => "Invalid parent"
72
+ * )
73
+ * ).toSubsetEqualLeft([{message: "Invalid child"}])
74
+ * expect(
75
+ * withFallbackMessage(
76
+ * t.type({age: t.number}),
77
+ * () => "Invalid parent"
78
+ * )
79
+ * ).toSubsetEqualLeft([{message: "Invalid parent"}])
80
+ */
81
+ function withFallbackMessage(codec, message) {
82
+ return (0, io_ts_types_1.withValidate)(codec, (i, c) => {
83
+ return fp_ts_1.either.mapLeft((errors) => {
84
+ if (errors.find((error) => error.message)) {
85
+ return errors;
86
+ }
87
+ return [
88
+ {
89
+ value: i,
90
+ context: c,
91
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
92
+ message: message(i, c),
93
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
94
+ actual: i,
95
+ },
96
+ ];
97
+ })(codec.validate(i, c));
98
+ });
99
+ }
100
+ exports.withFallbackMessage = withFallbackMessage;
@@ -9,6 +9,7 @@ export { default as IntFromPixels } from "./IntFromPixels";
9
9
  export { default as NonEmptyString } from "./NonEmptyString";
10
10
  export { default as NonEmptyStringOrNull } from "./NonEmptyStringOrNull";
11
11
  export { default as NumberOrNull } from "./NumberOrNull";
12
+ export { default as NumberRange } from "./NumberRange";
12
13
  export { default as StringFromBoolean } from "./StringFromBoolean";
13
14
  export { default as StringFromNumber } from "./StringFromNumber";
14
15
  export { default as StringOrNull } from "./StringOrNull";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StringOrNull = exports.StringFromNumber = exports.StringFromBoolean = exports.NumberOrNull = exports.NonEmptyStringOrNull = exports.NonEmptyString = exports.IntFromPixels = exports.IntFromNumber = exports.Function = exports.DateFromTsMs = exports.DateFromStringOrNumber = exports.DateFromString = void 0;
3
+ exports.StringOrNull = exports.StringFromNumber = exports.StringFromBoolean = exports.NumberRange = exports.NumberOrNull = exports.NonEmptyStringOrNull = exports.NonEmptyString = exports.IntFromPixels = exports.IntFromNumber = exports.Function = exports.DateFromTsMs = exports.DateFromStringOrNumber = exports.DateFromString = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  (0, tslib_1.__exportStar)(require("./BasicTypes"), exports);
6
6
  var DateFromString_1 = require("./DateFromString");
@@ -21,6 +21,8 @@ var NonEmptyStringOrNull_1 = require("./NonEmptyStringOrNull");
21
21
  Object.defineProperty(exports, "NonEmptyStringOrNull", { enumerable: true, get: function () { return (0, tslib_1.__importDefault)(NonEmptyStringOrNull_1).default; } });
22
22
  var NumberOrNull_1 = require("./NumberOrNull");
23
23
  Object.defineProperty(exports, "NumberOrNull", { enumerable: true, get: function () { return (0, tslib_1.__importDefault)(NumberOrNull_1).default; } });
24
+ var NumberRange_1 = require("./NumberRange");
25
+ Object.defineProperty(exports, "NumberRange", { enumerable: true, get: function () { return (0, tslib_1.__importDefault)(NumberRange_1).default; } });
24
26
  var StringFromBoolean_1 = require("./StringFromBoolean");
25
27
  Object.defineProperty(exports, "StringFromBoolean", { enumerable: true, get: function () { return (0, tslib_1.__importDefault)(StringFromBoolean_1).default; } });
26
28
  var StringFromNumber_1 = require("./StringFromNumber");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prismicio/types-internal",
3
- "version": "2.2.0-alpha.2",
3
+ "version": "2.2.0-alpha.4",
4
4
  "description": "Prismic types for Custom Types and Prismic Data",
5
5
  "keywords": [
6
6
  "typescript",
@@ -0,0 +1,15 @@
1
+ export type Asset = {
2
+ id: string
3
+ last_modified: string
4
+ kind: "image" | "all"
5
+ filename?: string
6
+ extension?: string
7
+ size?: string
8
+ origin_url: string
9
+ url: string
10
+ width?: number
11
+ height?: number
12
+ notes?: string
13
+ credits?: string
14
+ alt?: string
15
+ }
@@ -1 +1,2 @@
1
+ export * from "./Asset"
1
2
  export * from "./WidgetKey"
@@ -1,15 +1,16 @@
1
+ import type { Asset } from "../../common"
1
2
  import type { Document, WidgetContent } from "../../content"
2
3
  import type { ImportDocument } from "../validators"
3
4
  import type { ImportField } from "../validators/fields/ImportField"
4
- import { Asset, convertNestableWidget, uidConverter } from "./fields"
5
+ import { convertNestableWidget, uidConverter } from "./fields"
5
6
 
6
7
  export function convertImportToContent(
7
8
  document: ImportDocument,
8
- images: Map<string, Asset>,
9
+ assets: Record<string, Asset>,
9
10
  ): Document {
10
11
  return Object.entries(document).reduce<Document>(
11
12
  (acc, [fieldKey, fieldValue]) => {
12
- const newFieldValue = convertWidget(fieldValue, images)
13
+ const newFieldValue = convertWidget(fieldValue, assets)
13
14
  return newFieldValue ? { ...acc, [fieldKey]: newFieldValue } : acc
14
15
  },
15
16
  {},
@@ -18,12 +19,12 @@ export function convertImportToContent(
18
19
 
19
20
  function convertWidget(
20
21
  field: ImportField,
21
- images: Map<string, Asset>,
22
+ assets: Record<string, Asset>,
22
23
  ): WidgetContent | undefined {
23
24
  switch (field.type) {
24
25
  case "UID":
25
26
  return uidConverter(field.value)
26
27
  default:
27
- return convertNestableWidget(field, images)
28
+ return convertNestableWidget(field, assets)
28
29
  }
29
30
  }
@@ -0,0 +1,16 @@
1
+ import type { GeoPointContent } from "../../../../content"
2
+ import type { ImportGeoPoint } from "../../../validators"
3
+
4
+ export const geopointConverter = (
5
+ field: ImportGeoPoint["value"],
6
+ ): GeoPointContent | undefined => {
7
+ if (field === null) return
8
+
9
+ return {
10
+ position: {
11
+ lat: field.latitude,
12
+ lng: field.longitude,
13
+ },
14
+ __TYPE__: "GeoPointContent",
15
+ }
16
+ }
@@ -1,34 +1,19 @@
1
+ import type { Asset } from "../../../../common"
1
2
  import type { ImageContent } from "../../../../content"
2
3
  import { mapValues, withOptionals } from "../../../../utils/Objects"
3
4
  import type { ImageField, ImportImage } from "../../../validators"
4
5
 
5
- export type Asset = {
6
- id: string
7
- last_modified: string
8
- kind: "image" | "all"
9
- filename?: string
10
- extension?: string
11
- size?: string
12
- origin_url: string
13
- url: string
14
- width?: number
15
- height?: number
16
- notes?: string
17
- credits?: string
18
- alt?: string
19
- }
20
-
21
6
  function convertImage(field: ImageField, image: Asset) {
22
7
  return withOptionals<Omit<ImageContent, "__TYPE__" | "thumbnails">>(
23
8
  {
24
9
  origin: {
25
10
  id: field.id,
26
11
  url: image.origin_url,
27
- width: image.width!,
28
- height: image.height!,
12
+ width: image.width ?? 0,
13
+ height: image.height ?? 0,
29
14
  },
30
- width: field.edit?.width ?? image.width!,
31
- height: field.edit?.height ?? image.height!,
15
+ width: field.edit?.width ?? image.width ?? 0,
16
+ height: field.edit?.height ?? image.height ?? 0,
32
17
  edit: {
33
18
  zoom: field.edit?.zoom ?? 1,
34
19
  crop: {
@@ -48,10 +33,10 @@ function convertImage(field: ImageField, image: Asset) {
48
33
 
49
34
  export const imageConverter = (
50
35
  field: ImportImage["value"],
51
- images: Map<string, Asset>,
36
+ assets: Record<string, Asset>,
52
37
  ): ImageContent | undefined => {
53
38
  const getImageById = (id: string): Asset => {
54
- const image = images.get(id)
39
+ const image = assets[id]
55
40
  if (!image) throw new Error(`Missing asset with id '${id}'`)
56
41
  return image
57
42
  }
@@ -0,0 +1,33 @@
1
+ import type { LinkContent } from "../../../../content"
2
+ import type { ImportLink } from "../../../validators"
3
+
4
+ export const linkConverter = (
5
+ field: ImportLink["value"],
6
+ ): LinkContent | undefined => {
7
+ if (field === null) {
8
+ return
9
+ }
10
+
11
+ switch (field.link_type) {
12
+ case "Web":
13
+ return {
14
+ value: {
15
+ url: field.url,
16
+ target: field.target,
17
+ __TYPE__: "ExternalLink",
18
+ },
19
+ __TYPE__: "LinkContent",
20
+ }
21
+ case "Document":
22
+ return {
23
+ value: {
24
+ id: field.id,
25
+ __TYPE__: "DocumentLink",
26
+ },
27
+ __TYPE__: "LinkContent",
28
+ }
29
+ // TODO: https://linear.app/prismic/issue/AGE-90/[content-validation-and-error-management]-link-to-media
30
+ case "Media":
31
+ return undefined
32
+ }
33
+ }
@@ -1,12 +1,14 @@
1
+ import type { Asset } from "../../../../common"
1
2
  import type { WidgetContent } from "../../../../content"
2
3
  import type { ImportNestable } from "../../../validators"
3
4
  import {
4
- Asset,
5
5
  booleanConverter,
6
6
  colorConverter,
7
7
  dateConverter,
8
8
  embedConverter,
9
+ geopointConverter,
9
10
  imageConverter,
11
+ linkConverter,
10
12
  numberConverter,
11
13
  selectConverter,
12
14
  textConverter,
@@ -15,7 +17,7 @@ import {
15
17
 
16
18
  export function convertNestableWidget(
17
19
  field: ImportNestable,
18
- images: Map<string, Asset>,
20
+ assets: Record<string, Asset>,
19
21
  ): WidgetContent | undefined {
20
22
  switch (field.type) {
21
23
  case "Boolean":
@@ -34,8 +36,12 @@ export function convertNestableWidget(
34
36
  return timestampConverter(field.value)
35
37
  case "Embed":
36
38
  return embedConverter(field.value)
39
+ case "GeoPoint":
40
+ return geopointConverter(field.value)
41
+ case "Link":
42
+ return linkConverter(field.value)
37
43
  case "Image":
38
- return imageConverter(field.value, images)
44
+ return imageConverter(field.value, assets)
39
45
  default:
40
46
  throw new Error(
41
47
  `Unsupported type of nestable converter ${JSON.stringify(field)}`,
@@ -2,7 +2,9 @@ export * from "./Boolean"
2
2
  export * from "./Color"
3
3
  export * from "./Date"
4
4
  export * from "./Embed"
5
+ export * from "./GeooPoint"
5
6
  export * from "./Image"
7
+ export * from "./Link"
6
8
  export * from "./Nestable"
7
9
  export * from "./Number"
8
10
  export * from "./Select"
@@ -0,0 +1,21 @@
1
+ import type { TypeOf } from "io-ts"
2
+ import * as t from "io-ts"
3
+
4
+ import { EmptyObjectOrElse, NumberRange } from "../../../../validators"
5
+ import { withFallbackMessage } from "../../../../validators/function"
6
+ import { ImportContent } from "../ImportContent"
7
+
8
+ const GeooPoint = withFallbackMessage(
9
+ t.strict({
10
+ latitude: NumberRange(-90, 90, "latitude"),
11
+ longitude: NumberRange(-180, 180, "longitude"),
12
+ }),
13
+ () =>
14
+ "GeoPoint must be an object with the properties `latitude` and `longitude` between the given ranges",
15
+ )
16
+
17
+ export const ImportGeoPoint = ImportContent(
18
+ "GeoPoint",
19
+ EmptyObjectOrElse(GeooPoint),
20
+ )
21
+ export type ImportGeoPoint = TypeOf<typeof ImportGeoPoint>
@@ -31,24 +31,11 @@ const ImageFieldCodec = AnyObject.pipe(
31
31
  )
32
32
 
33
33
  const ThumbnailsCodec = t.record(t.string, ImageFieldCodec)
34
+ type Thumbnails = t.TypeOf<typeof ThumbnailsCodec>
34
35
 
35
- export type ImageField = {
36
- id: string
37
- edit?: {
38
- x: number
39
- y: number
40
- width: number
41
- height: number
42
- zoom: number
43
- background: string
44
- }
45
- alt?: string | null
46
- credit?: string | null
47
- }
36
+ export type ImageField = t.TypeOf<typeof ImageFieldCodec>
48
37
 
49
- type ImageFieldWithThumbnails = ImageField & {
50
- thumbnails: Record<string, ImageField>
51
- }
38
+ type ImageFieldWithThumbnails = ImageField & { thumbnails: Thumbnails }
52
39
 
53
40
  const ImageFieldWithThumbnails = new t.Type(
54
41
  "ImageFieldWithThumbnails",
@@ -0,0 +1,51 @@
1
+ import type { OutputOf, TypeOf } from "io-ts"
2
+ import * as t from "io-ts"
3
+ import { withMessage } from "io-ts-types"
4
+
5
+ import { DocumentId } from "../../../../utils/DocumentId"
6
+ import { DefaultOrElse, String } from "../../../../validators"
7
+ import { ImportContent } from "../ImportContent"
8
+
9
+ const LinkTypeValidator = t.type({
10
+ link_type: withMessage(
11
+ t.union([t.literal("Web"), t.literal("Document"), t.literal("Media")]),
12
+ () => "The value must be `Web`, `Document` or `Media`",
13
+ ),
14
+ })
15
+
16
+ const WebLink = t.intersection([
17
+ t.type({
18
+ link_type: t.literal("Web"),
19
+ url: String,
20
+ }),
21
+ t.partial({
22
+ target: String,
23
+ }),
24
+ ])
25
+
26
+ const DocumentLink = t.type({
27
+ link_type: t.literal("Document"),
28
+ id: DocumentId,
29
+ })
30
+
31
+ const MediaLink = t.type({
32
+ link_type: t.literal("Media"),
33
+ id: String,
34
+ })
35
+
36
+ const Link = LinkTypeValidator.pipe(t.union([WebLink, DocumentLink, MediaLink]))
37
+
38
+ // This is the default value for the link
39
+ const AnyLink = t.type({
40
+ link_type: t.literal("Any"),
41
+ })
42
+
43
+ export const ImportLink = ImportContent(
44
+ "Link",
45
+ DefaultOrElse<
46
+ TypeOf<typeof AnyLink>,
47
+ TypeOf<typeof Link>,
48
+ OutputOf<typeof Link>
49
+ >(AnyLink)(Link),
50
+ )
51
+ export type ImportLink = TypeOf<typeof ImportLink>
@@ -1,9 +1,11 @@
1
1
  import type { NestableWidget } from "../../../../customtypes"
2
- import { ImportBoolean } from "."
2
+ import { ImportBoolean } from "./Boolean"
3
3
  import { ImportColor } from "./Color"
4
4
  import { ImportDate } from "./Date"
5
5
  import { ImportEmbed } from "./Embed"
6
+ import { ImportGeoPoint } from "./GeoPoint"
6
7
  import { ImportImage } from "./Image"
8
+ import { ImportLink } from "./Link"
7
9
  import { ImportNumber } from "./Number"
8
10
  import { ImportSelect } from "./Select"
9
11
  import { ImportText } from "./Text"
@@ -18,6 +20,8 @@ export type ImportNestable =
18
20
  | ImportDate
19
21
  | ImportTimestamp
20
22
  | ImportEmbed
23
+ | ImportLink
24
+ | ImportGeoPoint
21
25
  | ImportImage
22
26
 
23
27
  export const ImportNestable = {
@@ -31,6 +35,8 @@ export const ImportNestable = {
31
35
  ImportDate.is(u) ||
32
36
  ImportTimestamp.is(u) ||
33
37
  ImportEmbed.is(u) ||
38
+ ImportLink.is(u) ||
39
+ ImportGeoPoint.is(u) ||
34
40
  ImportImage.is(u)
35
41
  )
36
42
  },
@@ -52,10 +58,14 @@ export const ImportNestable = {
52
58
  return ImportDate
53
59
  case "Timestamp":
54
60
  return ImportTimestamp
55
- case "Image":
56
- return ImportImage
57
61
  case "Embed":
58
62
  return ImportEmbed
63
+ case "Link":
64
+ return ImportLink
65
+ case "Image":
66
+ return ImportImage
67
+ case "GeoPoint":
68
+ return ImportGeoPoint
59
69
  default:
60
70
  throw new Error(`Unsupported type of nestable field ${field.type}`)
61
71
  }
@@ -2,7 +2,9 @@ export * from "./Boolean"
2
2
  export * from "./Color"
3
3
  export * from "./Date"
4
4
  export * from "./Embed"
5
+ export * from "./GeoPoint"
5
6
  export * from "./Image"
7
+ export * from "./Link"
6
8
  export * from "./Nestable"
7
9
  export * from "./Number"
8
10
  export * from "./Select"
@@ -0,0 +1,9 @@
1
+ import { withMessage } from "io-ts-types"
2
+
3
+ import { String } from "../validators"
4
+ import { refineType } from "../validators/function"
5
+
6
+ export const DocumentId = withMessage(
7
+ refineType(String, "DocumentId", (s) => s.length === 16),
8
+ () => "DocumentId must be a 16 character string",
9
+ )
@@ -0,0 +1,51 @@
1
+ import * as t from "io-ts"
2
+
3
+ import { Number } from "./BasicTypes"
4
+
5
+ /**
6
+ * Creates a custom runtime type for validating that a number falls within a specified range.
7
+ *
8
+ * @param min - The minimum value of the range.
9
+ * @param max - The maximum value of the range.
10
+ * @param fieldName - The name of the field being validated (used in error messages).
11
+ * @returns A runtime type representing the number range validation.
12
+ *
13
+ * * @example
14
+ * // Creating a custom runtime type for age validation
15
+ * const AgeType = numberInRange(18, 99, 'Age');
16
+ *
17
+ * // Valid age
18
+ * const validAgeResult = AgeType.decode(25);
19
+ * if (t.isRight(validAgeResult)) {
20
+ * console.log('Valid age:', validAgeResult.right); // Output: Valid age: 25
21
+ * } else {
22
+ * console.error('Invalid age:', t.left(validAgeResult).map(t.reporter.report));
23
+ * }
24
+ *
25
+ * @example
26
+ * // Invalid age
27
+ * const invalidAgeResult = AgeType.decode(15);
28
+ * if (t.isRight(invalidAgeResult)) {
29
+ * console.log('Valid age:', invalidAgeResult.right);
30
+ * } else {
31
+ * console.error('Invalid age:', t.left(invalidAgeResult).map(t.reporter.report));
32
+ * }
33
+ */
34
+ export default (min: number, max: number, fieldName: string) =>
35
+ Number.pipe(
36
+ new t.Type<number, number, number>(
37
+ "numberInRange",
38
+ (u: unknown): u is number => Number.is(u),
39
+ (u: number, context) => {
40
+ if (u > max || u < min) {
41
+ return t.failure(
42
+ u,
43
+ context,
44
+ `${fieldName} must be a number between ${min} and ${max}`,
45
+ )
46
+ }
47
+ return t.success(u)
48
+ },
49
+ t.identity,
50
+ ),
51
+ )
@@ -1,6 +1,7 @@
1
1
  import { either } from "fp-ts"
2
2
  import { pipe } from "fp-ts/function"
3
3
  import * as t from "io-ts"
4
+ import { withValidate } from "io-ts-types"
4
5
  import { mapOutput } from "io-ts-types/mapOutput"
5
6
 
6
7
  export function nullable<A, O>(c: t.Type<A, O>) {
@@ -69,3 +70,46 @@ export function addType<A, O extends object, I, T extends string>(
69
70
  ): t.Type<A, O & { __TYPE__: T }, I> {
70
71
  return mapOutput(codec, (o) => ({ ...o, __TYPE__: t } as const))
71
72
  }
73
+
74
+ /**
75
+ * Returns a clone of the given codec that tries to find sub-error with message already set.
76
+ * If there is such error it just returns sub-errors array.
77
+ * If there is no such error it generates new error with given message.
78
+ *
79
+ * @example
80
+ * expect(
81
+ * withFallbackMessage(
82
+ * t.type({age: withMessage(t.number, () => 'Invalid child')}),
83
+ * () => "Invalid parent"
84
+ * )
85
+ * ).toSubsetEqualLeft([{message: "Invalid child"}])
86
+ * expect(
87
+ * withFallbackMessage(
88
+ * t.type({age: t.number}),
89
+ * () => "Invalid parent"
90
+ * )
91
+ * ).toSubsetEqualLeft([{message: "Invalid parent"}])
92
+ */
93
+ export function withFallbackMessage<C extends t.Any>(
94
+ codec: C,
95
+ message: (i: t.InputOf<C>, c: t.Context) => string,
96
+ ): C {
97
+ return withValidate(codec, (i, c) => {
98
+ return either.mapLeft((errors: t.Errors) => {
99
+ if (errors.find((error) => error.message)) {
100
+ return errors
101
+ }
102
+
103
+ return [
104
+ {
105
+ value: i,
106
+ context: c,
107
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
108
+ message: message(i, c),
109
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
110
+ actual: i,
111
+ },
112
+ ]
113
+ })(codec.validate(i, c))
114
+ })
115
+ }
@@ -9,6 +9,7 @@ export { default as IntFromPixels } from "./IntFromPixels"
9
9
  export { default as NonEmptyString } from "./NonEmptyString"
10
10
  export { default as NonEmptyStringOrNull } from "./NonEmptyStringOrNull"
11
11
  export { default as NumberOrNull } from "./NumberOrNull"
12
+ export { default as NumberRange } from "./NumberRange"
12
13
  export { default as StringFromBoolean } from "./StringFromBoolean"
13
14
  export { default as StringFromNumber } from "./StringFromNumber"
14
15
  export { default as StringOrNull } from "./StringOrNull"