ts-fortress 1.0.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 (318) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +324 -0
  3. package/dist/array/array-of-length.d.mts +6 -0
  4. package/dist/array/array-of-length.d.mts.map +1 -0
  5. package/dist/array/array-of-length.mjs +50 -0
  6. package/dist/array/array-of-length.mjs.map +1 -0
  7. package/dist/array/array.d.mts +6 -0
  8. package/dist/array/array.d.mts.map +1 -0
  9. package/dist/array/array.mjs +39 -0
  10. package/dist/array/array.mjs.map +1 -0
  11. package/dist/array/index.d.mts +5 -0
  12. package/dist/array/index.d.mts.map +1 -0
  13. package/dist/array/index.mjs +5 -0
  14. package/dist/array/index.mjs.map +1 -0
  15. package/dist/array/non-empty-array.d.mts +6 -0
  16. package/dist/array/non-empty-array.d.mts.map +1 -0
  17. package/dist/array/non-empty-array.mjs +46 -0
  18. package/dist/array/non-empty-array.mjs.map +1 -0
  19. package/dist/array/tuple.d.mts +9 -0
  20. package/dist/array/tuple.d.mts.map +1 -0
  21. package/dist/array/tuple.mjs +47 -0
  22. package/dist/array/tuple.mjs.map +1 -0
  23. package/dist/branded/brand.d.mts +11 -0
  24. package/dist/branded/brand.d.mts.map +1 -0
  25. package/dist/branded/brand.mjs +29 -0
  26. package/dist/branded/brand.mjs.map +1 -0
  27. package/dist/branded/index.d.mts +5 -0
  28. package/dist/branded/index.d.mts.map +1 -0
  29. package/dist/branded/index.mjs +20 -0
  30. package/dist/branded/index.mjs.map +1 -0
  31. package/dist/branded/number/finite-number.d.mts +3 -0
  32. package/dist/branded/number/finite-number.d.mts.map +1 -0
  33. package/dist/branded/number/finite-number.mjs +14 -0
  34. package/dist/branded/number/finite-number.mjs.map +1 -0
  35. package/dist/branded/number/index.d.mts +17 -0
  36. package/dist/branded/number/index.d.mts.map +1 -0
  37. package/dist/branded/number/index.mjs +17 -0
  38. package/dist/branded/number/index.mjs.map +1 -0
  39. package/dist/branded/number/int.d.mts +3 -0
  40. package/dist/branded/number/int.d.mts.map +1 -0
  41. package/dist/branded/number/int.mjs +14 -0
  42. package/dist/branded/number/int.mjs.map +1 -0
  43. package/dist/branded/number/int16.d.mts +3 -0
  44. package/dist/branded/number/int16.d.mts.map +1 -0
  45. package/dist/branded/number/int16.mjs +26 -0
  46. package/dist/branded/number/int16.mjs.map +1 -0
  47. package/dist/branded/number/int32.d.mts +3 -0
  48. package/dist/branded/number/int32.d.mts.map +1 -0
  49. package/dist/branded/number/int32.mjs +22 -0
  50. package/dist/branded/number/int32.mjs.map +1 -0
  51. package/dist/branded/number/non-negative-finite-number.d.mts +3 -0
  52. package/dist/branded/number/non-negative-finite-number.d.mts.map +1 -0
  53. package/dist/branded/number/non-negative-finite-number.mjs +14 -0
  54. package/dist/branded/number/non-negative-finite-number.mjs.map +1 -0
  55. package/dist/branded/number/non-zero-finite-number.d.mts +3 -0
  56. package/dist/branded/number/non-zero-finite-number.d.mts.map +1 -0
  57. package/dist/branded/number/non-zero-finite-number.mjs +14 -0
  58. package/dist/branded/number/non-zero-finite-number.mjs.map +1 -0
  59. package/dist/branded/number/non-zero-int.d.mts +3 -0
  60. package/dist/branded/number/non-zero-int.d.mts.map +1 -0
  61. package/dist/branded/number/non-zero-int.mjs +14 -0
  62. package/dist/branded/number/non-zero-int.mjs.map +1 -0
  63. package/dist/branded/number/non-zero-safe-int.d.mts +3 -0
  64. package/dist/branded/number/non-zero-safe-int.d.mts.map +1 -0
  65. package/dist/branded/number/non-zero-safe-int.mjs +14 -0
  66. package/dist/branded/number/non-zero-safe-int.mjs.map +1 -0
  67. package/dist/branded/number/positive-finite-number.d.mts +3 -0
  68. package/dist/branded/number/positive-finite-number.d.mts.map +1 -0
  69. package/dist/branded/number/positive-finite-number.mjs +22 -0
  70. package/dist/branded/number/positive-finite-number.mjs.map +1 -0
  71. package/dist/branded/number/positive-int.d.mts +3 -0
  72. package/dist/branded/number/positive-int.d.mts.map +1 -0
  73. package/dist/branded/number/positive-int.mjs +23 -0
  74. package/dist/branded/number/positive-int.mjs.map +1 -0
  75. package/dist/branded/number/positive-safe-int.d.mts +3 -0
  76. package/dist/branded/number/positive-safe-int.d.mts.map +1 -0
  77. package/dist/branded/number/positive-safe-int.mjs +24 -0
  78. package/dist/branded/number/positive-safe-int.mjs.map +1 -0
  79. package/dist/branded/number/safe-int.d.mts +3 -0
  80. package/dist/branded/number/safe-int.d.mts.map +1 -0
  81. package/dist/branded/number/safe-int.mjs +14 -0
  82. package/dist/branded/number/safe-int.mjs.map +1 -0
  83. package/dist/branded/number/safe-uint.d.mts +3 -0
  84. package/dist/branded/number/safe-uint.d.mts.map +1 -0
  85. package/dist/branded/number/safe-uint.mjs +23 -0
  86. package/dist/branded/number/safe-uint.mjs.map +1 -0
  87. package/dist/branded/number/uint.d.mts +3 -0
  88. package/dist/branded/number/uint.d.mts.map +1 -0
  89. package/dist/branded/number/uint.mjs +22 -0
  90. package/dist/branded/number/uint.mjs.map +1 -0
  91. package/dist/branded/number/uint16.d.mts +3 -0
  92. package/dist/branded/number/uint16.d.mts.map +1 -0
  93. package/dist/branded/number/uint16.mjs +26 -0
  94. package/dist/branded/number/uint16.mjs.map +1 -0
  95. package/dist/branded/number/uint32.d.mts +3 -0
  96. package/dist/branded/number/uint32.d.mts.map +1 -0
  97. package/dist/branded/number/uint32.mjs +24 -0
  98. package/dist/branded/number/uint32.mjs.map +1 -0
  99. package/dist/branded/simple-branded-number.d.mts +3 -0
  100. package/dist/branded/simple-branded-number.d.mts.map +1 -0
  101. package/dist/branded/simple-branded-number.mjs +15 -0
  102. package/dist/branded/simple-branded-number.mjs.map +1 -0
  103. package/dist/branded/simple-branded-string.d.mts +3 -0
  104. package/dist/branded/simple-branded-string.d.mts.map +1 -0
  105. package/dist/branded/simple-branded-string.mjs +15 -0
  106. package/dist/branded/simple-branded-string.mjs.map +1 -0
  107. package/dist/compose/index.d.mts +4 -0
  108. package/dist/compose/index.d.mts.map +1 -0
  109. package/dist/compose/index.mjs +4 -0
  110. package/dist/compose/index.mjs.map +1 -0
  111. package/dist/compose/intersection.d.mts +19 -0
  112. package/dist/compose/intersection.d.mts.map +1 -0
  113. package/dist/compose/intersection.mjs +38 -0
  114. package/dist/compose/intersection.mjs.map +1 -0
  115. package/dist/compose/merge-records.d.mts +15 -0
  116. package/dist/compose/merge-records.d.mts.map +1 -0
  117. package/dist/compose/merge-records.mjs +48 -0
  118. package/dist/compose/merge-records.mjs.map +1 -0
  119. package/dist/compose/union.d.mts +14 -0
  120. package/dist/compose/union.d.mts.map +1 -0
  121. package/dist/compose/union.mjs +33 -0
  122. package/dist/compose/union.mjs.map +1 -0
  123. package/dist/enum/enum.d.mts +6 -0
  124. package/dist/enum/enum.d.mts.map +1 -0
  125. package/dist/enum/enum.mjs +34 -0
  126. package/dist/enum/enum.mjs.map +1 -0
  127. package/dist/enum/index.d.mts +3 -0
  128. package/dist/enum/index.d.mts.map +1 -0
  129. package/dist/enum/index.mjs +3 -0
  130. package/dist/enum/index.mjs.map +1 -0
  131. package/dist/enum/uint-range.d.mts +8 -0
  132. package/dist/enum/uint-range.d.mts.map +1 -0
  133. package/dist/enum/uint-range.mjs +40 -0
  134. package/dist/enum/uint-range.mjs.map +1 -0
  135. package/dist/globals.d.mts +3 -0
  136. package/dist/index.d.mts +10 -0
  137. package/dist/index.d.mts.map +1 -0
  138. package/dist/index.mjs +50 -0
  139. package/dist/index.mjs.map +1 -0
  140. package/dist/primitives/bigint.d.mts +4 -0
  141. package/dist/primitives/bigint.d.mts.map +1 -0
  142. package/dist/primitives/bigint.mjs +12 -0
  143. package/dist/primitives/bigint.mjs.map +1 -0
  144. package/dist/primitives/boolean.d.mts +4 -0
  145. package/dist/primitives/boolean.d.mts.map +1 -0
  146. package/dist/primitives/boolean.mjs +12 -0
  147. package/dist/primitives/boolean.mjs.map +1 -0
  148. package/dist/primitives/index.d.mts +8 -0
  149. package/dist/primitives/index.d.mts.map +1 -0
  150. package/dist/primitives/index.mjs +8 -0
  151. package/dist/primitives/index.mjs.map +1 -0
  152. package/dist/primitives/null.d.mts +3 -0
  153. package/dist/primitives/null.d.mts.map +1 -0
  154. package/dist/primitives/null.mjs +11 -0
  155. package/dist/primitives/null.mjs.map +1 -0
  156. package/dist/primitives/number.d.mts +4 -0
  157. package/dist/primitives/number.d.mts.map +1 -0
  158. package/dist/primitives/number.mjs +12 -0
  159. package/dist/primitives/number.mjs.map +1 -0
  160. package/dist/primitives/string.d.mts +4 -0
  161. package/dist/primitives/string.d.mts.map +1 -0
  162. package/dist/primitives/string.mjs +12 -0
  163. package/dist/primitives/string.mjs.map +1 -0
  164. package/dist/primitives/symbol.d.mts +3 -0
  165. package/dist/primitives/symbol.d.mts.map +1 -0
  166. package/dist/primitives/symbol.mjs +11 -0
  167. package/dist/primitives/symbol.mjs.map +1 -0
  168. package/dist/primitives/undefined.d.mts +3 -0
  169. package/dist/primitives/undefined.d.mts.map +1 -0
  170. package/dist/primitives/undefined.mjs +11 -0
  171. package/dist/primitives/undefined.mjs.map +1 -0
  172. package/dist/record/index.d.mts +8 -0
  173. package/dist/record/index.d.mts.map +1 -0
  174. package/dist/record/index.mjs +8 -0
  175. package/dist/record/index.mjs.map +1 -0
  176. package/dist/record/key-value-record.d.mts +7 -0
  177. package/dist/record/key-value-record.d.mts.map +1 -0
  178. package/dist/record/key-value-record.mjs +52 -0
  179. package/dist/record/key-value-record.mjs.map +1 -0
  180. package/dist/record/keyof.d.mts +7 -0
  181. package/dist/record/keyof.d.mts.map +1 -0
  182. package/dist/record/keyof.mjs +15 -0
  183. package/dist/record/keyof.mjs.map +1 -0
  184. package/dist/record/omit.d.mts +8 -0
  185. package/dist/record/omit.d.mts.map +1 -0
  186. package/dist/record/omit.mjs +39 -0
  187. package/dist/record/omit.mjs.map +1 -0
  188. package/dist/record/optional.d.mts +5 -0
  189. package/dist/record/optional.d.mts.map +1 -0
  190. package/dist/record/optional.mjs +8 -0
  191. package/dist/record/optional.mjs.map +1 -0
  192. package/dist/record/partial.d.mts +13 -0
  193. package/dist/record/partial.d.mts.map +1 -0
  194. package/dist/record/partial.mjs +128 -0
  195. package/dist/record/partial.mjs.map +1 -0
  196. package/dist/record/pick.d.mts +8 -0
  197. package/dist/record/pick.d.mts.map +1 -0
  198. package/dist/record/pick.mjs +39 -0
  199. package/dist/record/pick.mjs.map +1 -0
  200. package/dist/record/record.d.mts +21 -0
  201. package/dist/record/record.d.mts.map +1 -0
  202. package/dist/record/record.mjs +53 -0
  203. package/dist/record/record.mjs.map +1 -0
  204. package/dist/tsconfig.json +1 -0
  205. package/dist/type.d.mts +27 -0
  206. package/dist/type.d.mts.map +1 -0
  207. package/dist/type.mjs +2 -0
  208. package/dist/type.mjs.map +1 -0
  209. package/dist/types.d.mts +2 -0
  210. package/dist/unknown.d.mts +3 -0
  211. package/dist/unknown.d.mts.map +1 -0
  212. package/dist/unknown.mjs +18 -0
  213. package/dist/unknown.mjs.map +1 -0
  214. package/dist/utils/create-assert-fn.d.mts +3 -0
  215. package/dist/utils/create-assert-fn.d.mts.map +1 -0
  216. package/dist/utils/create-assert-fn.mjs +12 -0
  217. package/dist/utils/create-assert-fn.mjs.map +1 -0
  218. package/dist/utils/create-cast-fn.d.mts +3 -0
  219. package/dist/utils/create-cast-fn.d.mts.map +1 -0
  220. package/dist/utils/create-cast-fn.mjs +13 -0
  221. package/dist/utils/create-cast-fn.mjs.map +1 -0
  222. package/dist/utils/create-is-fn.d.mts +3 -0
  223. package/dist/utils/create-is-fn.d.mts.map +1 -0
  224. package/dist/utils/create-is-fn.mjs +6 -0
  225. package/dist/utils/create-is-fn.mjs.map +1 -0
  226. package/dist/utils/create-primitive-type.d.mts +7 -0
  227. package/dist/utils/create-primitive-type.d.mts.map +1 -0
  228. package/dist/utils/create-primitive-type.mjs +24 -0
  229. package/dist/utils/create-primitive-type.mjs.map +1 -0
  230. package/dist/utils/create-type.d.mts +8 -0
  231. package/dist/utils/create-type.d.mts.map +1 -0
  232. package/dist/utils/create-type.mjs +19 -0
  233. package/dist/utils/create-type.mjs.map +1 -0
  234. package/dist/utils/index.d.mts +7 -0
  235. package/dist/utils/index.d.mts.map +1 -0
  236. package/dist/utils/index.mjs +7 -0
  237. package/dist/utils/index.mjs.map +1 -0
  238. package/dist/utils/validation-error-message.d.mts +2 -0
  239. package/dist/utils/validation-error-message.d.mts.map +1 -0
  240. package/dist/utils/validation-error-message.mjs +13 -0
  241. package/dist/utils/validation-error-message.mjs.map +1 -0
  242. package/package.json +102 -0
  243. package/src/array/array-of-length.mts +76 -0
  244. package/src/array/array-of-length.test.mts +113 -0
  245. package/src/array/array.mts +62 -0
  246. package/src/array/array.test.mts +84 -0
  247. package/src/array/index.mts +4 -0
  248. package/src/array/non-empty-array.mts +70 -0
  249. package/src/array/non-empty-array.test.mts +113 -0
  250. package/src/array/tuple.mts +72 -0
  251. package/src/array/tuple.test.mts +122 -0
  252. package/src/branded/brand.mts +70 -0
  253. package/src/branded/index.mts +4 -0
  254. package/src/branded/number/finite-number.mts +15 -0
  255. package/src/branded/number/index.mts +16 -0
  256. package/src/branded/number/int.mts +13 -0
  257. package/src/branded/number/int16.mts +25 -0
  258. package/src/branded/number/int32.mts +21 -0
  259. package/src/branded/number/non-negative-finite-number.mts +18 -0
  260. package/src/branded/number/non-zero-finite-number.mts +15 -0
  261. package/src/branded/number/non-zero-int.mts +15 -0
  262. package/src/branded/number/non-zero-safe-int.mts +15 -0
  263. package/src/branded/number/positive-finite-number.mts +23 -0
  264. package/src/branded/number/positive-int.mts +24 -0
  265. package/src/branded/number/positive-safe-int.mts +25 -0
  266. package/src/branded/number/safe-int.mts +13 -0
  267. package/src/branded/number/safe-uint.mts +24 -0
  268. package/src/branded/number/uint.mts +21 -0
  269. package/src/branded/number/uint16.mts +25 -0
  270. package/src/branded/number/uint32.mts +23 -0
  271. package/src/branded/simple-branded-number.mts +18 -0
  272. package/src/branded/simple-branded-string.mts +18 -0
  273. package/src/combination.test.mts +287 -0
  274. package/src/compose/index.mts +3 -0
  275. package/src/compose/intersection.mts +206 -0
  276. package/src/compose/intersection.test.mts +231 -0
  277. package/src/compose/merge-records.mts +218 -0
  278. package/src/compose/merge-records.test.mts +90 -0
  279. package/src/compose/union.mts +77 -0
  280. package/src/compose/union.test.mts +92 -0
  281. package/src/enum/enum.mts +53 -0
  282. package/src/enum/enum.test.mts +63 -0
  283. package/src/enum/index.mts +2 -0
  284. package/src/enum/uint-range.mts +63 -0
  285. package/src/enum/uint-range.test.mts +66 -0
  286. package/src/globals.d.mts +3 -0
  287. package/src/index.mts +9 -0
  288. package/src/primitives/bigint.mts +13 -0
  289. package/src/primitives/boolean.mts +13 -0
  290. package/src/primitives/index.mts +7 -0
  291. package/src/primitives/null.mts +9 -0
  292. package/src/primitives/number.mts +13 -0
  293. package/src/primitives/string.mts +13 -0
  294. package/src/primitives/symbol.mts +10 -0
  295. package/src/primitives/undefined.mts +9 -0
  296. package/src/record/index.mts +7 -0
  297. package/src/record/key-value-record.mts +84 -0
  298. package/src/record/key-value-record.test.mts +96 -0
  299. package/src/record/keyof.mts +31 -0
  300. package/src/record/keyof.test.mts +73 -0
  301. package/src/record/omit.mts +94 -0
  302. package/src/record/omit.test.mts +116 -0
  303. package/src/record/optional.mts +16 -0
  304. package/src/record/partial.mts +186 -0
  305. package/src/record/partial.test.mts +261 -0
  306. package/src/record/pick.mts +98 -0
  307. package/src/record/pick.test.mts +116 -0
  308. package/src/record/record.mts +105 -0
  309. package/src/record/record.test.mts +253 -0
  310. package/src/type.mts +30 -0
  311. package/src/unknown.mts +17 -0
  312. package/src/utils/create-assert-fn.mts +14 -0
  313. package/src/utils/create-cast-fn.mts +15 -0
  314. package/src/utils/create-is-fn.mts +7 -0
  315. package/src/utils/create-primitive-type.mts +35 -0
  316. package/src/utils/create-type.mts +28 -0
  317. package/src/utils/index.mts +6 -0
  318. package/src/utils/validation-error-message.mts +17 -0
@@ -0,0 +1,287 @@
1
+ import { asInt, expectType, Result } from 'ts-data-forge';
2
+ import { array } from './array/index.mjs';
3
+ import { int } from './branded/index.mjs';
4
+ import { uintRange } from './enum/index.mjs';
5
+ import { number } from './primitives/index.mjs';
6
+ import { optional, pick, record } from './record/index.mjs';
7
+ import { type TypeOf } from './type.mjs';
8
+ import { unknown } from './unknown.mjs';
9
+
10
+ describe('nested record', () => {
11
+ const nestedRecord = record({
12
+ xs: array(int(asInt(2))),
13
+ rec: pick(
14
+ record({
15
+ a: uintRange({ start: 0, end: 11, defaultValue: 0 }),
16
+ b: uintRange({ start: 0, end: 11, defaultValue: 0 }),
17
+ c: optional(uintRange({ start: 3, end: 6, defaultValue: 3 })),
18
+ d: unknown(),
19
+ }),
20
+ ['a', 'b', 'c'],
21
+ ),
22
+ meta: number(100),
23
+ u: unknown(),
24
+ });
25
+
26
+ type NestedRecord = TypeOf<typeof nestedRecord>;
27
+
28
+ expectType<
29
+ NestedRecord,
30
+ Readonly<{
31
+ xs: readonly Int[];
32
+ rec: Readonly<{
33
+ a: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
34
+ b: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
35
+ c?: 3 | 4 | 5;
36
+ }>;
37
+ meta: number;
38
+ u: unknown;
39
+ }>
40
+ >('=');
41
+
42
+ expectType<typeof nestedRecord.defaultValue, NestedRecord>('=');
43
+
44
+ describe('is', () => {
45
+ test('truthy case', () => {
46
+ const x: UnknownRecord = {
47
+ xs: [1, 2, 3],
48
+ rec: {
49
+ a: 1,
50
+ b: 2,
51
+ c: 3,
52
+ },
53
+ meta: 3,
54
+ u: undefined,
55
+ };
56
+
57
+ if (nestedRecord.is(x)) {
58
+ expectType<typeof x, NestedRecord>('=');
59
+ } else {
60
+ expectType<typeof x, UnknownRecord>('=');
61
+ }
62
+
63
+ expect(nestedRecord.is(x)).toBe(true);
64
+ });
65
+
66
+ test('falsy case', () => {
67
+ const x: UnknownRecord = {
68
+ xs: [-1, 2.2, 3.3],
69
+ rec: {
70
+ a: 123,
71
+ b: 234,
72
+ c: 3,
73
+ },
74
+ meta: 345,
75
+ u: undefined,
76
+ };
77
+
78
+ if (nestedRecord.is(x)) {
79
+ expectType<typeof x, NestedRecord>('=');
80
+ } else {
81
+ expectType<typeof x, UnknownRecord>('=');
82
+ }
83
+
84
+ expect(nestedRecord.is(x)).toBe(false);
85
+ });
86
+ });
87
+
88
+ describe('validate', () => {
89
+ test('falsy case', () => {
90
+ const x: UnknownRecord = {
91
+ xs: [-1, 2.2, 3.3],
92
+ rec: {
93
+ a: 123,
94
+ b: 234,
95
+ c: 3,
96
+ },
97
+ meta: 345,
98
+ u: undefined,
99
+ };
100
+
101
+ expect(nestedRecord.validate(x).value).toStrictEqual([
102
+ `The value at record key "xs" is expected to be <(Finite & Int & not(NaNValue))[]>, but it is actually '[-1,2.2,3.3]'.`,
103
+ `The array element is expected to be <Finite & Int & not(NaNValue)>, but the actual value at index 1 is '2.2'.`,
104
+ `The value must satisfy the constraint corresponding to the brand keys: <Finite & Int & not(NaNValue)>, but it is actually '2.2'.`,
105
+ ]);
106
+ });
107
+ });
108
+
109
+ describe('fill', () => {
110
+ test('from an empty record', () => {
111
+ const x: UnknownRecord = {};
112
+
113
+ expect(nestedRecord.fill(x)).toStrictEqual({
114
+ xs: [],
115
+ rec: {
116
+ a: 0,
117
+ b: 0,
118
+ c: 3,
119
+ },
120
+ meta: 100,
121
+ u: undefined,
122
+ });
123
+ });
124
+
125
+ test('from a filled record', () => {
126
+ const x: UnknownRecord = {
127
+ xs: [-1, 2.2, 3.3],
128
+ rec: {
129
+ a: 123,
130
+ b: 234,
131
+ c: 3,
132
+ },
133
+ meta: 345,
134
+ u: undefined,
135
+ };
136
+
137
+ expect(nestedRecord.fill(x)).toStrictEqual({
138
+ xs: [-1, 2, 2],
139
+ rec: {
140
+ a: 0,
141
+ b: 0,
142
+ c: 3,
143
+ },
144
+ meta: 345,
145
+ u: undefined,
146
+ });
147
+ });
148
+
149
+ test('from a partial record', () => {
150
+ const x: UnknownRecord = {
151
+ xs: [11, 22],
152
+ rec: {
153
+ a: 3,
154
+ },
155
+ };
156
+
157
+ expect(nestedRecord.fill(x)).toStrictEqual({
158
+ xs: [11, 22],
159
+ rec: {
160
+ a: 3,
161
+ b: 0,
162
+ c: 3,
163
+ },
164
+ meta: 100,
165
+ u: undefined,
166
+ });
167
+ });
168
+
169
+ test('from a partial record with excess property', () => {
170
+ const x: UnknownRecord = {
171
+ xs: [11, 22],
172
+ rec: {
173
+ a: 3,
174
+ d: 9988,
175
+ },
176
+ u: undefined,
177
+ aaaaa: [9999],
178
+ };
179
+
180
+ expect(nestedRecord.fill(x)).toStrictEqual({
181
+ xs: [11, 22],
182
+ rec: {
183
+ a: 3,
184
+ b: 0,
185
+ c: 3,
186
+ },
187
+ meta: 100,
188
+ u: undefined,
189
+ });
190
+ });
191
+ });
192
+ });
193
+
194
+ describe('ymd', () => {
195
+ const ymd = record({
196
+ year: number(1900),
197
+ month: number(1),
198
+ date: number(1),
199
+ });
200
+
201
+ test('is', () => {
202
+ const x = {
203
+ year: 2000,
204
+ };
205
+
206
+ expect(ymd.is(x)).toBe(false);
207
+ });
208
+
209
+ test('fill', () => {
210
+ const x = {
211
+ year: 2000,
212
+ };
213
+
214
+ expect(ymd.fill(x)).toStrictEqual({
215
+ year: 2000,
216
+ month: 1,
217
+ date: 1,
218
+ });
219
+ });
220
+
221
+ test('assertion function', () => {
222
+ type YMD = TypeOf<typeof ymd>;
223
+
224
+ const assertIsYmd: (a: unknown) => asserts a is YMD = ymd.assertIs;
225
+
226
+ const x = {
227
+ year: 2000,
228
+ };
229
+
230
+ const f = (): void => {
231
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
232
+ // @ts-expect-error
233
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
234
+ x.month;
235
+
236
+ assertIsYmd(x); // throws an error
237
+
238
+ expectType<typeof x.month, number>('='); // no type error
239
+ };
240
+
241
+ expect(f).toThrow('The record is expected to have the key "month".');
242
+ });
243
+
244
+ test('validation function', () => {
245
+ const x = {
246
+ year: 2000,
247
+ };
248
+
249
+ const result = ymd.validate(x);
250
+
251
+ expect(Result.isErr(result)).toBe(true);
252
+
253
+ expect(result.value).toStrictEqual([
254
+ 'The record is expected to have the key "month".',
255
+ ]);
256
+ });
257
+ });
258
+
259
+ describe('ymd2', () => {
260
+ const ymd2 = record({
261
+ year: int(asInt(1900)),
262
+ month: uintRange({
263
+ defaultValue: 1,
264
+ start: 1,
265
+ end: 13,
266
+ }),
267
+ date: uintRange({
268
+ defaultValue: 1,
269
+ start: 1,
270
+ end: 32,
271
+ }),
272
+ });
273
+
274
+ type YMD2 = TypeOf<typeof ymd2>;
275
+
276
+ expectType<YMD2['month'], 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12>(
277
+ '=',
278
+ );
279
+
280
+ test('is', () => {
281
+ const x = {
282
+ year: 2000,
283
+ };
284
+
285
+ expect(ymd2.is(x)).toBe(false);
286
+ });
287
+ });
@@ -0,0 +1,3 @@
1
+ export * from './intersection.mjs';
2
+ export * from './merge-records.mjs';
3
+ export * from './union.mjs';
@@ -0,0 +1,206 @@
1
+ import { expectType, Result } from 'ts-data-forge';
2
+ import { type Type, type TypeOf } from '../type.mjs';
3
+ import {
4
+ createAssertFn,
5
+ createCastFn,
6
+ createIsFn,
7
+ validationErrorMessage,
8
+ } from '../utils/index.mjs';
9
+
10
+ export const intersection = <const Types extends NonEmptyArray<Type<unknown>>>(
11
+ types: Types,
12
+ defaultType: IntersectionType<Types>,
13
+ options?: Partial<
14
+ Readonly<{
15
+ typeName: string;
16
+ }>
17
+ >,
18
+ ): IntersectionType<Types> => {
19
+ type T = IntersectionTypeValue<Types>;
20
+
21
+ const typeNameFilled: string =
22
+ options?.typeName ??
23
+ `Intersection<${types.map((a) => a.typeName).join(', ')}>`;
24
+
25
+ const validate: Type<T>['validate'] = (a) => {
26
+ for (const type of types) {
27
+ const r = type.validate(a);
28
+
29
+ if (Result.isErr(r))
30
+ return Result.err([
31
+ validationErrorMessage(
32
+ a,
33
+ `The type of value is expected to match all types of { ${types
34
+ .map((t) => t.typeName)
35
+ .join(', ')} }`,
36
+ ),
37
+ ...r.value,
38
+ ]);
39
+ }
40
+
41
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
42
+ return Result.ok(a as T);
43
+ };
44
+
45
+ const is = createIsFn<T>(validate);
46
+
47
+ const fill: Type<T>['fill'] = (a) => (is(a) ? a : defaultType.fill(a));
48
+
49
+ return {
50
+ typeName: typeNameFilled,
51
+ defaultValue: defaultType.defaultValue,
52
+ fill,
53
+ validate,
54
+ is,
55
+ assertIs: createAssertFn(validate),
56
+ cast: createCastFn(validate),
57
+ };
58
+ };
59
+
60
+ type IntersectionType<Types extends NonEmptyArray<Type<unknown>>> = Type<
61
+ IntersectionTypeValue<Types>
62
+ >;
63
+
64
+ type IntersectionTypeValue<Types extends NonEmptyArray<Type<unknown>>> =
65
+ TsFortressInternal.IntersectionTypeValueImpl<Types>;
66
+
67
+ namespace TsFortressInternal {
68
+ export type IntersectionTypeValueImpl<
69
+ Types extends NonEmptyArray<Type<unknown>>,
70
+ > = Intersection<Cast0<UnwrapTypeList<Types>>>;
71
+
72
+ type Cast0<T> = [T] extends [NonEmptyArray<unknown>] ? T : never;
73
+ }
74
+
75
+ expectType<
76
+ IntersectionType<
77
+ [
78
+ Type<{
79
+ a: 0;
80
+ b: 0;
81
+ }>,
82
+ Type<{
83
+ b: 0;
84
+ c: 0;
85
+ }>,
86
+ ]
87
+ >,
88
+ Type<{
89
+ a: 0;
90
+ b: 0;
91
+ c: 0;
92
+ }>
93
+ >('=');
94
+
95
+ type UnwrapTypeList<Types extends readonly Type<unknown>[]> =
96
+ TsFortressInternal.UnwrapTypeImpl<Types>;
97
+
98
+ namespace TsFortressInternal {
99
+ export type UnwrapTypeImpl<Types extends readonly unknown[]> =
100
+ Types extends readonly []
101
+ ? []
102
+ : Types extends readonly [infer Head, ...infer Tail]
103
+ ? readonly [TypeOf<Cast1<Head>>, ...UnwrapTypeImpl<Tail>]
104
+ : never;
105
+
106
+ type Cast1<T> = [T] extends [Type<unknown>] ? T : never;
107
+ }
108
+
109
+ expectType<
110
+ TypeOf<
111
+ Type<{
112
+ a: 0;
113
+ b: 0;
114
+ }>
115
+ >,
116
+ {
117
+ a: 0;
118
+ b: 0;
119
+ }
120
+ >('=');
121
+
122
+ expectType<
123
+ UnwrapTypeList<
124
+ [
125
+ Type<{
126
+ a: 0;
127
+ b: 0;
128
+ }>,
129
+ Type<{
130
+ b: 0;
131
+ c: 0;
132
+ }>,
133
+ ]
134
+ >,
135
+ readonly [
136
+ {
137
+ a: 0;
138
+ b: 0;
139
+ },
140
+ {
141
+ b: 0;
142
+ c: 0;
143
+ },
144
+ ]
145
+ >('=');
146
+
147
+ expectType<
148
+ Intersection<
149
+ [
150
+ {
151
+ a: 0;
152
+ b: 0;
153
+ },
154
+ {
155
+ b: 0;
156
+ c: 0;
157
+ },
158
+ ]
159
+ >,
160
+ {
161
+ a: 0;
162
+ b: 0;
163
+ c: 0;
164
+ }
165
+ >('=');
166
+
167
+ expectType<
168
+ Intersection<
169
+ [
170
+ {
171
+ a: 0;
172
+ b: 0;
173
+ },
174
+ {
175
+ b: 0;
176
+ c: 0;
177
+ },
178
+ {
179
+ c: 0;
180
+ d: 0;
181
+ },
182
+ ]
183
+ >,
184
+ {
185
+ a: 0;
186
+ b: 0;
187
+ c: 0;
188
+ d: 0;
189
+ }
190
+ >('=');
191
+
192
+ expectType<
193
+ Intersection<
194
+ [
195
+ {
196
+ a: 0;
197
+ b: 0;
198
+ },
199
+ {
200
+ b: 1;
201
+ c: 0;
202
+ },
203
+ ]
204
+ >,
205
+ never
206
+ >('=');
@@ -0,0 +1,231 @@
1
+ import { expectType } from 'ts-data-forge';
2
+ import { uintRange } from '../enum/index.mjs';
3
+ import { number } from '../primitives/index.mjs';
4
+ import { record } from '../record/index.mjs';
5
+ import { type TypeOf } from '../type.mjs';
6
+ import { intersection } from './intersection.mjs';
7
+
8
+ describe('intersection', () => {
9
+ describe('merge records', () => {
10
+ const targetType = intersection(
11
+ [
12
+ record({ x: number(), y: number() }),
13
+ record({ z: number(), w: number() }),
14
+ ],
15
+ record({
16
+ x: number(),
17
+ y: number(),
18
+ z: number(),
19
+ w: number(),
20
+ }),
21
+ );
22
+
23
+ type TargetType = TypeOf<typeof targetType>;
24
+
25
+ expectType<
26
+ TargetType,
27
+ Readonly<{
28
+ x: number;
29
+ y: number;
30
+ z: number;
31
+ w: number;
32
+ }>
33
+ >('=');
34
+
35
+ expectType<typeof targetType.defaultValue, TargetType>('=');
36
+
37
+ describe('is', () => {
38
+ test('truthy case', () => {
39
+ const x: unknown = { x: 0, y: 1, z: 2, w: 3 };
40
+
41
+ if (targetType.is(x)) {
42
+ expectType<typeof x, TargetType>('=');
43
+ } else {
44
+ expectType<typeof x, unknown>('=');
45
+ }
46
+
47
+ expect(targetType.is(x)).toBe(true);
48
+ });
49
+
50
+ test('truthy case 2', () => {
51
+ const x: unknown = { x: 0, y: 1, z: 2, w: 3, a: 0, b: 0 };
52
+
53
+ if (targetType.is(x)) {
54
+ expectType<typeof x, TargetType>('=');
55
+ } else {
56
+ expectType<typeof x, unknown>('=');
57
+ }
58
+
59
+ expect(targetType.is(x)).toBe(true);
60
+ });
61
+
62
+ test('falsy case', () => {
63
+ const x: unknown = { x: 0, y: 1 };
64
+
65
+ if (targetType.is(x)) {
66
+ expectType<typeof x, TargetType>('=');
67
+ } else {
68
+ expectType<typeof x, unknown>('=');
69
+ }
70
+
71
+ expect(targetType.is(x)).toBe(false);
72
+ });
73
+ });
74
+
75
+ describe('validate', () => {
76
+ test('falsy case', () => {
77
+ expect(targetType.validate({ x: 0, y: 1 }).value).toStrictEqual([
78
+ 'The type of value is expected to match all types of { { x: number, y: number }, { z: number, w: number } }, but it is actually \'{"x":0,"y":1}\'.',
79
+ 'The record is expected to have the key "z".',
80
+ ]);
81
+ });
82
+ });
83
+
84
+ describe('fill', () => {
85
+ test('noop', () => {
86
+ const x: unknown = { x: 0, y: 1, z: 2, w: 3 };
87
+
88
+ expect(targetType.fill(x)).toStrictEqual({ x: 0, y: 1, z: 2, w: 3 });
89
+ });
90
+
91
+ test('fill with the default value', () => {
92
+ const x = { x: 0, y: 1, z: 2 };
93
+
94
+ expect(targetType.fill(x)).toStrictEqual({ x: 0, y: 1, z: 2, w: 0 });
95
+ });
96
+ });
97
+ });
98
+
99
+ describe('merge primitives', () => {
100
+ const targetType = intersection([number(1), number(2)], number());
101
+
102
+ type TargetType = TypeOf<typeof targetType>;
103
+
104
+ expectType<TargetType, number>('=');
105
+
106
+ expectType<typeof targetType.defaultValue, TargetType>('=');
107
+
108
+ describe('is', () => {
109
+ test('truthy case', () => {
110
+ const x: unknown = 0;
111
+
112
+ if (targetType.is(x)) {
113
+ expectType<typeof x, TargetType>('=');
114
+ } else {
115
+ expectType<typeof x, unknown>('=');
116
+ }
117
+
118
+ expect(targetType.is(x)).toBe(true);
119
+ });
120
+
121
+ test('falsy case', () => {
122
+ const x: unknown = '3';
123
+
124
+ if (targetType.is(x)) {
125
+ expectType<typeof x, TargetType>('=');
126
+ } else {
127
+ expectType<typeof x, unknown>('=');
128
+ }
129
+
130
+ expect(targetType.is(x)).toBe(false);
131
+ });
132
+ });
133
+
134
+ describe('validate', () => {
135
+ test('falsy case', () => {
136
+ expect(targetType.validate('aaa').value).toStrictEqual([
137
+ 'The type of value is expected to match all types of { number, number }, but it is actually \'"aaa"\'.',
138
+ 'The value is expected to be <number>, but it is actually \'"aaa"\'.',
139
+ ]);
140
+ });
141
+ });
142
+
143
+ describe('fill', () => {
144
+ test('noop', () => {
145
+ const x: unknown = 3;
146
+
147
+ expect(targetType.fill(x)).toBe(3);
148
+ });
149
+
150
+ test('fill with the default value', () => {
151
+ const x = { x: 0, y: 1, z: 2 };
152
+
153
+ expect(targetType.fill(x)).toBe(0);
154
+ });
155
+ });
156
+ });
157
+
158
+ describe('merge unions', () => {
159
+ const targetType = intersection(
160
+ [uintRange({ start: 0, end: 5 }), uintRange({ start: 1, end: 7 })],
161
+ uintRange({ start: 1, end: 4 }),
162
+ );
163
+
164
+ type TargetType = TypeOf<typeof targetType>;
165
+
166
+ expectType<TargetType, 1 | 2 | 3 | 4>('=');
167
+
168
+ expectType<typeof targetType.defaultValue, TargetType>('=');
169
+
170
+ describe('is', () => {
171
+ test('truthy case', () => {
172
+ const x: unknown = 3;
173
+
174
+ if (targetType.is(x)) {
175
+ expectType<typeof x, TargetType>('=');
176
+ } else {
177
+ expectType<typeof x, unknown>('=');
178
+ }
179
+
180
+ expect(targetType.is(x)).toBe(true);
181
+ });
182
+
183
+ test('falsy case 1', () => {
184
+ const x: unknown = '3';
185
+
186
+ if (targetType.is(x)) {
187
+ expectType<typeof x, TargetType>('=');
188
+ } else {
189
+ expectType<typeof x, unknown>('=');
190
+ }
191
+
192
+ expect(targetType.is(x)).toBe(false);
193
+ });
194
+
195
+ test('falsy case 2', () => {
196
+ const x: unknown = 7;
197
+
198
+ if (targetType.is(x)) {
199
+ expectType<typeof x, TargetType>('=');
200
+ } else {
201
+ expectType<typeof x, unknown>('=');
202
+ }
203
+
204
+ expect(targetType.is(x)).toBe(false);
205
+ });
206
+ });
207
+
208
+ describe('validate', () => {
209
+ test('falsy case', () => {
210
+ expect(targetType.validate(7).value).toStrictEqual([
211
+ "The type of value is expected to match all types of { uintRange(0, 5), uintRange(1, 7) }, but it is actually '7'.",
212
+ "The value is expected to be an integer between 0 and 4, but it is actually '7'.",
213
+ ]);
214
+ });
215
+ });
216
+
217
+ describe('fill', () => {
218
+ test('noop', () => {
219
+ const x: unknown = 3;
220
+
221
+ expect(targetType.fill(x)).toBe(3);
222
+ });
223
+
224
+ test('fill with the default value', () => {
225
+ const x = 9;
226
+
227
+ expect(targetType.fill(x)).toBe(1);
228
+ });
229
+ });
230
+ });
231
+ });