inferred-types 0.26.0 → 0.27.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.
@@ -5,6 +5,7 @@
5
5
  "Aint",
6
6
  "Dasherize",
7
7
  "dasherized",
8
+ "fooy",
8
9
  "ruleset",
9
10
  "tokei",
10
11
  "Uncapitalize"
package/dist/index.d.ts CHANGED
@@ -579,26 +579,52 @@ O extends (...args: any[]) => any = (...args: any[]) => any> = SimplifyObject<{
579
579
  declare type Get<T, K> = K extends `${infer FK}.${infer L}` ? FK extends keyof T ? Get<T[FK], L> : never : K extends keyof T ? T[K] : never;
580
580
 
581
581
  /**
582
- * **MapTo**
582
+ * Expresses whether an option is "opt" (optional) or "req" (required)
583
+ */
584
+ declare type OptRequired = "opt" | "req";
585
+
586
+ /**
587
+ * Expresses relationship between inputs/outputs:
588
+ */
589
+ declare enum MapDirection {
590
+ /** every input results in 0:M outputs */
591
+ OneToMany = "I -> O[]",
592
+ /** every input results in 0:1 outputs */
593
+ OneToOne = "I -> O",
594
+ /** every input is an array of type I and reduced to a single O */
595
+ ManyToOne = "I[] -> O"
596
+ }
597
+ declare type MapDirectionVal = EnumValues<MapDirection>;
598
+ declare type MapInput<I, IR, D extends MapDirectionVal> = D extends MapDirection.OneToMany | "I -> O[]" ? IR extends "opt" ? I | undefined : I : D extends MapDirection.OneToOne | "I -> O" ? IR extends "opt" ? I | undefined : I : D extends MapDirection.ManyToOne | "I[] -> O" ? IR extends "opt" ? I[] | undefined : I[] : never;
599
+ declare type MapOutput<O, OR, D extends MapDirectionVal> = D extends MapDirection.OneToMany | "I -> O[]" ? OR extends "opt" ? O[] : [O, ...O[]] : D extends MapDirection.OneToOne | "I -> O" ? OR extends "opt" ? O | null : O : D extends MapDirection.ManyToOne | "I[] -> O" ? OR extends "opt" ? O | null : O : never;
600
+ /**
601
+ * **MapTo<I, O>**
583
602
  *
584
- * Maps from one type `I` to another `O[]`
603
+ * A mapping function between an input type `I` and output type `O`.
585
604
  *
586
- * **Note:** because the output is an array you can easily support `1:1` and `1:M` mappings
587
- * but not a filtering operation (e.g., `1:0`); if you need this then use `MapToWithFiltering`
588
- * instead.
605
+ * **Note:** this type is designed to guide the userland mapping; refer
606
+ * to `MapFn` if you want the type output by the `mapFn()` utility.
589
607
  */
590
- declare type MapTo<I extends {}, O extends {}> = (i: I) => O[];
608
+ declare type MapTo<I, O, IR extends OptRequired = "req", D extends MapDirectionVal = MapDirection.OneToMany, OR extends OptRequired = "opt"> = IR extends "opt" ? (source?: MapInput<I, IR, D>) => MapOutput<O, OR, D> : (source: MapInput<I, IR, D>) => MapOutput<O, OR, D>;
609
+ declare type MapFnOutput<I, O, S, OR extends OptRequired, D extends MapDirectionVal> = D extends "I -> O[]" | MapDirection.OneToMany ? S extends I[] ? OR extends "opt" ? O[] : [O, ...O[]] : OR extends "opt" ? O[] | null : O[] : D extends MapDirection.OneToOne | "I -> O" ? S extends I[] ? OR extends "opt" ? O[] : [O, ...O[]] : OR extends "opt" ? O | null : O : D extends MapDirection.ManyToOne | "I[] -> O" ? S extends I[][] ? OR extends "opt" ? O[] : [O, ...O[]] : OR extends "opt" ? O | null : O : never;
610
+ declare type MapFnInput<I, IR extends OptRequired, D extends MapDirectionVal> = D extends "I -> O[]" | MapDirection.OneToMany ? IR extends "opt" ? I | I[] | undefined : I | I[] : D extends MapDirection.OneToOne | "I -> O" ? IR extends "opt" ? I | I[] | undefined : I | I[] : D extends MapDirection.ManyToOne | "I[] -> O" ? IR extends "opt" ? I[] | I[][] | undefined : I[] | I[][] : never;
591
611
  /**
592
- * **MapToWithFiltering**
612
+ * The mapping function provided by the `mapFn()` utility. This _fn_
613
+ * is intended to be used in two ways:
593
614
  *
594
- * Maps from one type `I` to another `(O | null)[]`
595
- *
596
- * **Note:** because the output is an array you can easily support `1:1` and `1:M` mappings
597
- * and the allowance of the conversion to result in a `null` value also means that filter
598
- * out an input value entirely is possible. If you don't need _filtering_ then use the
599
- * `MapTo` type instead.
615
+ * 1. Iterative:
616
+ * ```ts
617
+ * const m = mapTo<I,O>(i => [ ... ]);
618
+ * // maps inputs to outputs
619
+ * const out = inputs.map(m);
620
+ * ```
621
+ * 2. Block:
622
+ * ```ts
623
+ * // maps inputs to outputs (filtering or splitting where approp)
624
+ * const out2 = m(inputs);
625
+ * ```
600
626
  */
601
- declare type MapToWithFiltering<I extends {}, O extends {}> = (i: I) => O[] | null;
627
+ declare type MapFn<I, O, IR extends OptRequired = "req", D extends MapDirectionVal = MapDirection.OneToMany, OR extends OptRequired = "opt"> = IR extends "opt" ? <S extends MapFnInput<I, IR, D>>(source?: S) => MapFnOutput<I, O, S, OR, D> : <S extends MapFnInput<I, IR, D>>(source: S) => MapFnOutput<I, O, S, OR, D>;
602
628
 
603
629
  /**
604
630
  * Given a dictionary of type `<T>`, this utility function will
@@ -1061,6 +1087,77 @@ declare type _DU<T extends string> = T extends Lowercase<T> ? T : `-${Lowercase<
1061
1087
  */
1062
1088
  declare type DashUppercase<T extends string> = HasUppercase<T> extends false ? T : T extends `${infer C0}${infer C1}${infer R}` ? `${_DU<C0>}${_DU<C1>}${DashUppercase<R>}` : T extends `${infer C0}${infer R}` ? `${_DU<C0>}${DashUppercase<R>}` : "";
1063
1089
 
1090
+ /**
1091
+ * Converts a Tuple type into a _union_ of the tuple elements
1092
+ * ```ts
1093
+ * const arr = [1, 3, 5] as const;
1094
+ * // 1 | 3 | 5
1095
+ * type U = TupleToUnion<typeof arr>;
1096
+ * ```
1097
+ */
1098
+ declare type TupleToUnion<T> = Mutable<T> extends any[] ? Mutable<T>[number] : never;
1099
+
1100
+ /**
1101
+ * LastInUnion<1 | 2> = 2.
1102
+ */
1103
+ declare type LastInUnion<U> = UnionToIntersection<U extends unknown ? (x: U) => 0 : never> extends (x: infer L) => 0 ? L : never;
1104
+ /**
1105
+ * UnionToTuple<1 | 2> = [1, 2].
1106
+ */
1107
+ declare type UnionToTuple<U, Last = LastInUnion<U>> = [U] extends [never] ? [] : [...UnionToTuple<Exclude<U, Last>>, Last];
1108
+
1109
+ declare type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
1110
+
1111
+ declare type OneToOne = `1:1`;
1112
+ declare type OneToMany = `1:M`;
1113
+ declare type OneToZero = `1:0`;
1114
+ declare type ZeroToOne = `0:1`;
1115
+ declare type ZeroToMany = `0:M`;
1116
+ declare type ZeroToZero = `0:0`;
1117
+ declare type ManyToMany = "M:M";
1118
+ declare type ManyToOne = "M:1";
1119
+ declare type ManyToZero = "M:0";
1120
+ declare type CardinalityNode = "0" | "1" | "M";
1121
+ /**
1122
+ * Cardinality which expects a singular input and requires
1123
+ * 1 or many outputs.
1124
+ *
1125
+ * Note: choose `CardinalityFilter1` if you want to allow output
1126
+ * to have no outputs.
1127
+ */
1128
+ declare type Cardinality1 = OneToOne | OneToMany;
1129
+ /**
1130
+ * Cardinality which expects a singular input and maps to 0,
1131
+ * 1, or many outputs.
1132
+ */
1133
+ declare type CardinalityFilter1 = OneToOne | OneToMany | OneToZero;
1134
+ /**
1135
+ * Cardinality which expects a singular input which is allowed to be
1136
+ * _undefined_ or the expected type.
1137
+ */
1138
+ declare type Cardinality0 = ZeroToOne | ZeroToMany | OneToOne | OneToMany;
1139
+ /**
1140
+ * Cardinality which expects a singular input -- which is allowed to be
1141
+ * _undefined_ -- and maps to 0,
1142
+ * 1, or many outputs.
1143
+ */
1144
+ declare type CardinalityFilter0 = ZeroToOne | ZeroToMany | OneToOne | OneToMany | OneToZero | ZeroToZero;
1145
+ declare type CardinalityExplicit = `${number}:${number}`;
1146
+ /**
1147
+ * Cardinality of any sort between two types
1148
+ */
1149
+ declare type Cardinality = CardinalityFilter0 | CardinalityFilter1 | ManyToMany | ManyToOne | ManyToZero | CardinalityExplicit;
1150
+ declare type CardinalityTuple<T extends Cardinality> = UnionToTuple<T>;
1151
+ /**
1152
+ * The first or _input_ part of the Cardinality relationship
1153
+ */
1154
+ declare type CardinalityIn<T extends Cardinality> = T extends `${infer IN}:${string}` ? IN : never;
1155
+ /**
1156
+ * The second or _output_ part of the Cardinality relationship
1157
+ */
1158
+ declare type CardinalityOut<T extends Cardinality> = T extends `${string}:${infer OUT}` ? OUT : never;
1159
+ declare type CardinalityInput<T, C extends Cardinality> = CardinalityTuple<C>[0] extends 0 ? T | undefined : CardinalityTuple<C>[0] extends 1 ? T : T[];
1160
+
1064
1161
  /**
1065
1162
  * Converts a string literal into a _dasherized_ format while ignoring _exterior_ whitespace.
1066
1163
  *
@@ -1277,15 +1374,6 @@ declare type DictArray<T> = Array<{
1277
1374
  [K in keyof T]: DictArrayKv<K, T>;
1278
1375
  }[keyof T]>;
1279
1376
 
1280
- /**
1281
- * LastInUnion<1 | 2> = 2.
1282
- */
1283
- declare type LastInUnion<U> = UnionToIntersection<U extends unknown ? (x: U) => 0 : never> extends (x: infer L) => 0 ? L : never;
1284
- /**
1285
- * UnionToTuple<1 | 2> = [1, 2].
1286
- */
1287
- declare type UnionToTuple<U, Last = LastInUnion<U>> = [U] extends [never] ? [] : [...UnionToTuple<Exclude<U, Last>>, Last];
1288
-
1289
1377
  /**
1290
1378
  * Returns the _first_ key in an object.
1291
1379
  *
@@ -1314,18 +1402,6 @@ declare type FirstKeyValue<T extends object> = FirstKey<T> extends keyof T ? T[F
1314
1402
  */
1315
1403
  declare type FirstOfEach<T extends readonly any[][]> = T[number][0] extends T[number][number] ? T[number][0] : never;
1316
1404
 
1317
- /**
1318
- * Converts a Tuple type into a _union_ of the tuple elements
1319
- * ```ts
1320
- * const arr = [1, 3, 5] as const;
1321
- * // 1 | 3 | 5
1322
- * type U = TupleToUnion<typeof arr>;
1323
- * ```
1324
- */
1325
- declare type TupleToUnion<T> = Mutable<T> extends any[] ? Mutable<T>[number] : never;
1326
-
1327
- declare type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
1328
-
1329
1405
  /**
1330
1406
  * Typescript utility which receives `T` as shape which resembles `DictArray<D>`
1331
1407
  * and if the type `D` can be inferred it is returned.
@@ -1624,32 +1700,74 @@ declare function entries<N extends Narrowable, T extends Record<string, N>, I ex
1624
1700
  */
1625
1701
  declare function mapValues<N extends Narrowable, T extends Record<string, N>, V>(obj: T, valueMapper: (k: T[keyof T]) => V): { [K in keyof T]: V; };
1626
1702
 
1703
+ interface MapConfig<IR extends OptRequired = "req", D extends MapDirectionVal = "I -> O[]", OR extends OptRequired = "opt"> {
1704
+ input?: IR;
1705
+ output?: OR;
1706
+ direction?: D;
1707
+ }
1627
1708
  /**
1628
- * **mapTo**
1629
- *
1630
- * A utility function which maps one dictionary structure `I` to another `O`; which allows
1631
- * the transform to:
1632
- *
1633
- * - _split_ inputs into multiple outputs
1634
- * - _map_ 1:1 between input and output
1635
- * - _filter_ inputs which don't meet certain criteria (by returning `null`)
1636
- *
1637
- * This higher order function first asks for the mapping criteria:
1638
- * ```ts
1639
- * const mapper = mapTo<I,O>(i => i.name
1640
- * ? [
1641
- * { name: i.name, a: "static text", b: i.products.length }
1642
- * ]
1643
- * : null
1644
- * );
1645
- * ```
1709
+ * Provides a mapper function using the default configuration
1710
+ */
1711
+ declare type MapperApiDefault = <I, O>(map: MapTo<I, O>) => MapFn<I, O>;
1712
+ /**
1713
+ * Provides opportunity to configure _input_ and _output_ relationships
1714
+ * prior to providing a mapping function.
1715
+ */
1716
+ declare type MapperApiConfigured = <IR extends OptRequired = "req", D extends MapDirectionVal = "I -> O[]", OR extends OptRequired = "opt">(config: MapConfig<IR, D, OR>) => <I, O>(map: MapTo<I, O, IR, D, OR>) => MapFn<I, O, IR, D, OR>;
1717
+ /**
1718
+ * **mapTo** _utility_
1646
1719
  *
1647
- * and now you'll have a _mapper_ variable will be assigned as a mapping fn:
1720
+ * This utility creates a strongly typed data mapper which maps from one
1721
+ * known source `I` to another `O`:
1648
1722
  * ```ts
1649
- * function (source: I | I[]): O[]
1723
+ * const mapper = mapTo<I, O>(i => [{
1724
+ * foo: i.bar
1725
+ * }]);
1650
1726
  * ```
1651
1727
  */
1652
- declare const mapTo: <I extends {}, O extends {}>(cb: MapToWithFiltering<I, O>) => (source: I | I[]) => O[];
1728
+ declare const mapToFn: <I, O>(map: (source: I) => O[]) => <S extends I | I[]>(source: S) => S extends I[] ? O[] : O[] | null;
1729
+ /**
1730
+ * Provides a `config` method which allows the relationships between _inputs_
1731
+ * and _outputs_ to be configured.
1732
+ */
1733
+ declare const mapToDict: {
1734
+ /** configure the relationship between the _inputs_ and _outputs_ */
1735
+ config: <IR extends OptRequired = "req", D extends "I -> O[]" | "I -> O" | "I[] -> O" = "I -> O[]", OR extends OptRequired = "opt">(c?: MapConfig<IR, D, OR>) => {
1736
+ /**
1737
+ * create your mapping with your recently setup configuration:
1738
+ * ```ts
1739
+ * const mapper = mapTo
1740
+ * .config({ ... })
1741
+ * .map<I, O>(i => [{
1742
+ * foo: i.bar
1743
+ * }]);
1744
+ * ```
1745
+ */
1746
+ map<I, O>(map: MapTo<I, O, IR, D, OR>): MapFn<I, O, IR, D, OR>;
1747
+ };
1748
+ };
1749
+ /**
1750
+ * **mapTo** _utility_
1751
+ *
1752
+ * This utility creates a strongly typed data mapper which maps from one
1753
+ * known source `I` to another `O`.
1754
+ */
1755
+ declare const mapTo: (<I, O>(map: (source: I) => O[]) => <S extends I | I[]>(source: S) => S extends I[] ? O[] : O[] | null) & {
1756
+ /** configure the relationship between the _inputs_ and _outputs_ */
1757
+ config: <IR extends OptRequired = "req", D extends "I -> O[]" | "I -> O" | "I[] -> O" = "I -> O[]", OR extends OptRequired = "opt">(c?: MapConfig<IR, D, OR>) => {
1758
+ /**
1759
+ * create your mapping with your recently setup configuration:
1760
+ * ```ts
1761
+ * const mapper = mapTo
1762
+ * .config({ ... })
1763
+ * .map<I, O>(i => [{
1764
+ * foo: i.bar
1765
+ * }]);
1766
+ * ```
1767
+ */
1768
+ map<I, O>(map: MapTo<I, O, IR, D, OR>): MapFn<I, O, IR, D, OR>;
1769
+ };
1770
+ };
1653
1771
 
1654
1772
  /**
1655
1773
  * converts an array of strings `["a", "b", "c"]` into a more type friendly
@@ -1927,4 +2045,4 @@ interface IFluentConfigurator<C> {
1927
2045
  */
1928
2046
  declare function FluentConfigurator<I>(initial?: I): IFluentConfigurator<{}>;
1929
2047
 
1930
- export { AllCaps, Alpha, AlphaNumeric, Api, ApiFunction, ApiValue, AppendToDictionary, AppendToObject, ArrConcat, Array$1 as Array, ArrayConverter, AsArray, AssertExtends, Bracket, Break, CamelCase, CapitalizeWords, ClosingBracket, Condition, ConditionFilter, Configurator, Constructor, DashToSnake, DashUppercase, Dasherize, DefinePropertiesApi, DictArrApi, DictArray, DictArrayFilterCallback, DictArrayKv, DictChangeValue, DictFromKv, DictKvTuple, DictPartialApplication, DictPrependWithFn, DictReturnValues, DynamicRule, DynamicRuleSet, Email, EnumValues, ExpandRecursively, ExpectExtends, ExplicitFunction, ExtendsClause, ExtendsNarrowlyClause, FilterContains, FilterDefn, FilterEnds, FilterEquals, FilterFn, FilterGreaterThan, FilterIs, FilterLessThan, FilterNotEqual, FilterStarts, FinalReturn, First, FirstKey, FirstKeyValue, FirstOfEach, FluentConfigurator, FluentFunction, FromDictArray, FunctionType, GeneralDictionary, Get, HasUppercase, IConfigurator, IFluentConfigurator, If, IfExtends, IfExtendsThen, Include, Includes, IsArray, IsBoolean, IsBooleanLiteral, IsCapitalized, IsFalse, IsFunction, IsLiteral, IsNumericLiteral, IsObject, IsString, IsStringLiteral, IsTrue, KebabCase, KeyValue, KeyedRecord, Keys, KeysWithValue, KvFrom, KvTuple, LastInUnion, LeftWhitespace, Length, LogicFunction, LogicalCombinator, LowerAllCaps, LowerAlpha, MapTo, MapToWithFiltering, MaybeFalse, MaybeTrue, Model, Mutable, MutableProps, MutationFunction, MutationIdentity, Narrowable, NonAlpha, NonNumericKeys, NonStringKeys, Not, NotFilter, Numeric, NumericFilter, NumericKeys, NumericString, ObjectType, Opaque, OpeningBracket, OptionalKeys, OptionalProps, Parenthesis, PascalCase, Pluralize, PrivateKey, PrivateKeys, PublicKeys, Punctuation, PureFluentApi, Replace, RequireProps, RequiredKeys, RequiredProps, Retain, RightWhitespace, RuleDefinition, RuntimeProp, RuntimeType, SameKeys, SecondOfEach, SimplifyObject, SnakeCase, SpecialCharacters, StringDelimiter, StringFilter, StringKeys, StringLength, ToFluent, Transformer, Trim, TrimLeft, TrimRight, TupleToUnion, Type, TypeApi, TypeCondition, TypeDefinition, TypeGuard, TypeOptions, UndefinedArrayIsUnknown, UndefinedTreatment, UndefinedValue, UnionToIntersection, UnionToTuple, UniqueDictionary, UniqueForProp, Uniqueness, UnwrapNot, UpperAlpha, ValueTuple, ValueTypeFunc, ValueTypes, Where, WhereNot, Whitespace, Widen, WithNumericKeys, WithStringKeys, WithValue, ZipCode, and, api, arrayToKeyLookup, arrayToObject, asArray, condition, createFnWithProps, createMutationFunction, defineProperties, defineType, dictArr, dictToKv, dictionaryTransform, entries, filter, filterDictArray, fnWithProps, groupBy, idLiteral, idTypeGuard, identity, ifTypeOf, isArray, isBoolean, isFalse, isFunction, isNotFilter, isNull, isNumber, isNumericFilter, isObject, isString, isSymbol, isTrue, isType, isUndefined, keys, kindLiteral, kv, kvToDict, literal, mapTo, mapValues, nameLiteral, not, or, randomString, readonlyFnWithProps, ruleSet, strArrayToDict, type, typeApi, uuid, valueTypes, withValue };
2048
+ export { AllCaps, Alpha, AlphaNumeric, Api, ApiFunction, ApiValue, AppendToDictionary, AppendToObject, ArrConcat, Array$1 as Array, ArrayConverter, AsArray, AssertExtends, Bracket, Break, CamelCase, CapitalizeWords, Cardinality, Cardinality0, Cardinality1, CardinalityExplicit, CardinalityFilter0, CardinalityFilter1, CardinalityIn, CardinalityInput, CardinalityNode, CardinalityOut, CardinalityTuple, ClosingBracket, Condition, ConditionFilter, Configurator, Constructor, DashToSnake, DashUppercase, Dasherize, DefinePropertiesApi, DictArrApi, DictArray, DictArrayFilterCallback, DictArrayKv, DictChangeValue, DictFromKv, DictKvTuple, DictPartialApplication, DictPrependWithFn, DictReturnValues, DynamicRule, DynamicRuleSet, Email, EnumValues, ExpandRecursively, ExpectExtends, ExplicitFunction, ExtendsClause, ExtendsNarrowlyClause, FilterContains, FilterDefn, FilterEnds, FilterEquals, FilterFn, FilterGreaterThan, FilterIs, FilterLessThan, FilterNotEqual, FilterStarts, FinalReturn, First, FirstKey, FirstKeyValue, FirstOfEach, FluentConfigurator, FluentFunction, FromDictArray, FunctionType, GeneralDictionary, Get, HasUppercase, IConfigurator, IFluentConfigurator, If, IfExtends, IfExtendsThen, Include, Includes, IsArray, IsBoolean, IsBooleanLiteral, IsCapitalized, IsFalse, IsFunction, IsLiteral, IsNumericLiteral, IsObject, IsString, IsStringLiteral, IsTrue, KebabCase, KeyValue, KeyedRecord, Keys, KeysWithValue, KvFrom, KvTuple, LastInUnion, LeftWhitespace, Length, LogicFunction, LogicalCombinator, LowerAllCaps, LowerAlpha, ManyToMany, ManyToOne, ManyToZero, MapConfig, MapDirection, MapDirectionVal, MapFn, MapFnInput, MapFnOutput, MapInput, MapOutput, MapTo, MapperApiConfigured, MapperApiDefault, MaybeFalse, MaybeTrue, Model, Mutable, MutableProps, MutationFunction, MutationIdentity, Narrowable, NonAlpha, NonNumericKeys, NonStringKeys, Not, NotFilter, Numeric, NumericFilter, NumericKeys, NumericString, ObjectType, OneToMany, OneToOne, OneToZero, Opaque, OpeningBracket, OptRequired, OptionalKeys, OptionalProps, Parenthesis, PascalCase, Pluralize, PrivateKey, PrivateKeys, PublicKeys, Punctuation, PureFluentApi, Replace, RequireProps, RequiredKeys, RequiredProps, Retain, RightWhitespace, RuleDefinition, RuntimeProp, RuntimeType, SameKeys, SecondOfEach, SimplifyObject, SnakeCase, SpecialCharacters, StringDelimiter, StringFilter, StringKeys, StringLength, ToFluent, Transformer, Trim, TrimLeft, TrimRight, TupleToUnion, Type, TypeApi, TypeCondition, TypeDefinition, TypeGuard, TypeOptions, UndefinedArrayIsUnknown, UndefinedTreatment, UndefinedValue, UnionToIntersection, UnionToTuple, UniqueDictionary, UniqueForProp, Uniqueness, UnwrapNot, UpperAlpha, ValueTuple, ValueTypeFunc, ValueTypes, Where, WhereNot, Whitespace, Widen, WithNumericKeys, WithStringKeys, WithValue, ZeroToMany, ZeroToOne, ZeroToZero, ZipCode, and, api, arrayToKeyLookup, arrayToObject, asArray, condition, createFnWithProps, createMutationFunction, defineProperties, defineType, dictArr, dictToKv, dictionaryTransform, entries, filter, filterDictArray, fnWithProps, groupBy, idLiteral, idTypeGuard, identity, ifTypeOf, isArray, isBoolean, isFalse, isFunction, isNotFilter, isNull, isNumber, isNumericFilter, isObject, isString, isSymbol, isTrue, isType, isUndefined, keys, kindLiteral, kv, kvToDict, literal, mapTo, mapToDict, mapToFn, mapValues, nameLiteral, not, or, randomString, readonlyFnWithProps, ruleSet, strArrayToDict, type, typeApi, uuid, valueTypes, withValue };
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ __export(src_exports, {
23
23
  Configurator: () => Configurator,
24
24
  ExplicitFunction: () => ExplicitFunction,
25
25
  FluentConfigurator: () => FluentConfigurator,
26
+ MapDirection: () => MapDirection,
26
27
  Model: () => Model,
27
28
  MutationIdentity: () => MutationIdentity,
28
29
  and: () => and,
@@ -67,6 +68,8 @@ __export(src_exports, {
67
68
  kvToDict: () => kvToDict,
68
69
  literal: () => literal,
69
70
  mapTo: () => mapTo,
71
+ mapToDict: () => mapToDict,
72
+ mapToFn: () => mapToFn,
70
73
  mapValues: () => mapValues,
71
74
  nameLiteral: () => nameLiteral,
72
75
  not: () => not,
@@ -126,6 +129,14 @@ var valueTypes = {
126
129
  literalArray: (arr) => [arr, true]
127
130
  };
128
131
 
132
+ // src/types/dictionary/MapTo.ts
133
+ var MapDirection = /* @__PURE__ */ ((MapDirection2) => {
134
+ MapDirection2["OneToMany"] = "I -> O[]";
135
+ MapDirection2["OneToOne"] = "I -> O";
136
+ MapDirection2["ManyToOne"] = "I[] -> O";
137
+ return MapDirection2;
138
+ })(MapDirection || {});
139
+
129
140
  // src/utility/keys.ts
130
141
  function keys(obj, ...without) {
131
142
  const v = without.length > 0 ? Object.keys(obj).filter((k) => !without.includes(k)) : Object.keys(obj);
@@ -579,14 +590,34 @@ function mapValues(obj, valueMapper) {
579
590
  }
580
591
 
581
592
  // src/utility/dictionary/mapTo.ts
582
- var mapTo = (cb) => (source) => {
583
- if (Array.isArray(source)) {
584
- return source.flatMap((i) => cb(i)).filter((i) => i !== null);
585
- } else {
586
- const result = cb(source);
587
- return result ? result : [];
588
- }
593
+ var mapper = (config = {}) => (map) => {
594
+ config = {
595
+ input: "req",
596
+ output: "opt",
597
+ direction: "I -> O[]",
598
+ ...config
599
+ };
600
+ const fn = (source) => {
601
+ const isArray2 = config.direction === "I -> O[]" && Array.isArray(source) ? true : config.direction === "I[] -> O" && Array.isArray(source) && Array.isArray(source[0]) ? true : false;
602
+ if (isArray2) {
603
+ return source.flatMap(map);
604
+ } else {
605
+ return map(source);
606
+ }
607
+ };
608
+ return fn;
609
+ };
610
+ var mapToFn = (map) => {
611
+ return mapper()(map);
612
+ };
613
+ var mapToDict = {
614
+ config: (c = { input: "req", output: "opt", direction: "I -> O[]" }) => ({
615
+ map(map) {
616
+ return mapper(c)(map);
617
+ }
618
+ })
589
619
  };
620
+ var mapTo = createFnWithProps(mapToFn, mapToDict);
590
621
 
591
622
  // src/utility/dictionary/strArrayToDict.ts
592
623
  function strArrayToDict(...strings) {
@@ -754,6 +785,7 @@ function Model(name) {
754
785
  Configurator,
755
786
  ExplicitFunction,
756
787
  FluentConfigurator,
788
+ MapDirection,
757
789
  Model,
758
790
  MutationIdentity,
759
791
  and,
@@ -798,6 +830,8 @@ function Model(name) {
798
830
  kvToDict,
799
831
  literal,
800
832
  mapTo,
833
+ mapToDict,
834
+ mapToFn,
801
835
  mapValues,
802
836
  nameLiteral,
803
837
  not,
package/dist/index.mjs CHANGED
@@ -41,6 +41,14 @@ var valueTypes = {
41
41
  literalArray: (arr) => [arr, true]
42
42
  };
43
43
 
44
+ // src/types/dictionary/MapTo.ts
45
+ var MapDirection = /* @__PURE__ */ ((MapDirection2) => {
46
+ MapDirection2["OneToMany"] = "I -> O[]";
47
+ MapDirection2["OneToOne"] = "I -> O";
48
+ MapDirection2["ManyToOne"] = "I[] -> O";
49
+ return MapDirection2;
50
+ })(MapDirection || {});
51
+
44
52
  // src/utility/keys.ts
45
53
  function keys(obj, ...without) {
46
54
  const v = without.length > 0 ? Object.keys(obj).filter((k) => !without.includes(k)) : Object.keys(obj);
@@ -494,14 +502,34 @@ function mapValues(obj, valueMapper) {
494
502
  }
495
503
 
496
504
  // src/utility/dictionary/mapTo.ts
497
- var mapTo = (cb) => (source) => {
498
- if (Array.isArray(source)) {
499
- return source.flatMap((i) => cb(i)).filter((i) => i !== null);
500
- } else {
501
- const result = cb(source);
502
- return result ? result : [];
503
- }
505
+ var mapper = (config = {}) => (map) => {
506
+ config = {
507
+ input: "req",
508
+ output: "opt",
509
+ direction: "I -> O[]",
510
+ ...config
511
+ };
512
+ const fn = (source) => {
513
+ const isArray2 = config.direction === "I -> O[]" && Array.isArray(source) ? true : config.direction === "I[] -> O" && Array.isArray(source) && Array.isArray(source[0]) ? true : false;
514
+ if (isArray2) {
515
+ return source.flatMap(map);
516
+ } else {
517
+ return map(source);
518
+ }
519
+ };
520
+ return fn;
521
+ };
522
+ var mapToFn = (map) => {
523
+ return mapper()(map);
524
+ };
525
+ var mapToDict = {
526
+ config: (c = { input: "req", output: "opt", direction: "I -> O[]" }) => ({
527
+ map(map) {
528
+ return mapper(c)(map);
529
+ }
530
+ })
504
531
  };
532
+ var mapTo = createFnWithProps(mapToFn, mapToDict);
505
533
 
506
534
  // src/utility/dictionary/strArrayToDict.ts
507
535
  function strArrayToDict(...strings) {
@@ -668,6 +696,7 @@ export {
668
696
  Configurator,
669
697
  ExplicitFunction,
670
698
  FluentConfigurator,
699
+ MapDirection,
671
700
  Model,
672
701
  MutationIdentity,
673
702
  and,
@@ -712,6 +741,8 @@ export {
712
741
  kvToDict,
713
742
  literal,
714
743
  mapTo,
744
+ mapToDict,
745
+ mapToFn,
715
746
  mapValues,
716
747
  nameLiteral,
717
748
  not,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inferred-types",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "Functions which provide useful type inference on TS projects",
5
5
  "license": "MIT",
6
6
  "author": "Ken Snyder<ken@ken.net>",
@@ -0,0 +1,80 @@
1
+ import { UnionToTuple } from "../type-conversion";
2
+
3
+ export type OneToOne = `1:1`;
4
+ export type OneToMany = `1:M`;
5
+ export type OneToZero = `1:0`;
6
+ export type ZeroToOne = `0:1`;
7
+ export type ZeroToMany = `0:M`;
8
+ export type ZeroToZero = `0:0`;
9
+ export type ManyToMany = "M:M";
10
+ export type ManyToOne = "M:1";
11
+ export type ManyToZero = "M:0";
12
+
13
+ export type CardinalityNode = "0" | "1" | "M";
14
+
15
+ /**
16
+ * Cardinality which expects a singular input and requires
17
+ * 1 or many outputs.
18
+ *
19
+ * Note: choose `CardinalityFilter1` if you want to allow output
20
+ * to have no outputs.
21
+ */
22
+ export type Cardinality1 = OneToOne | OneToMany;
23
+
24
+ /**
25
+ * Cardinality which expects a singular input and maps to 0,
26
+ * 1, or many outputs.
27
+ */
28
+ export type CardinalityFilter1 = OneToOne | OneToMany | OneToZero;
29
+
30
+ /**
31
+ * Cardinality which expects a singular input which is allowed to be
32
+ * _undefined_ or the expected type.
33
+ */
34
+ export type Cardinality0 = ZeroToOne | ZeroToMany | OneToOne | OneToMany;
35
+
36
+ /**
37
+ * Cardinality which expects a singular input -- which is allowed to be
38
+ * _undefined_ -- and maps to 0,
39
+ * 1, or many outputs.
40
+ */
41
+ export type CardinalityFilter0 =
42
+ | ZeroToOne
43
+ | ZeroToMany
44
+ | OneToOne
45
+ | OneToMany
46
+ | OneToZero
47
+ | ZeroToZero;
48
+
49
+ export type CardinalityExplicit = `${number}:${number}`;
50
+
51
+ /**
52
+ * Cardinality of any sort between two types
53
+ */
54
+ export type Cardinality =
55
+ | CardinalityFilter0
56
+ | CardinalityFilter1
57
+ | ManyToMany
58
+ | ManyToOne
59
+ | ManyToZero
60
+ | CardinalityExplicit;
61
+
62
+ export type CardinalityTuple<T extends Cardinality> = UnionToTuple<T>;
63
+
64
+ /**
65
+ * The first or _input_ part of the Cardinality relationship
66
+ */
67
+ export type CardinalityIn<T extends Cardinality> = T extends `${infer IN}:${string}` ? IN : never;
68
+
69
+ /**
70
+ * The second or _output_ part of the Cardinality relationship
71
+ */
72
+ export type CardinalityOut<T extends Cardinality> = T extends `${string}:${infer OUT}`
73
+ ? OUT
74
+ : never;
75
+
76
+ export type CardinalityInput<T, C extends Cardinality> = CardinalityTuple<C>[0] extends 0
77
+ ? T | undefined
78
+ : CardinalityTuple<C>[0] extends 1
79
+ ? T
80
+ : T[];
@@ -9,6 +9,7 @@ export * from "./CamelCase";
9
9
  export * from "./CapitalizeWords";
10
10
  export * from "./DashToSnake";
11
11
  export * from "./DashUppercase";
12
+ export * from "./Cardinality";
12
13
  export * from "./Dasherize";
13
14
  export * from "./HasUppercase";
14
15
  export * from "./IsCapitalized";
@@ -1,23 +1,136 @@
1
+ import { OptRequired } from "src/types/literal-unions";
2
+ import { EnumValues } from "../EnumValues";
3
+
1
4
  /**
2
- * **MapTo**
3
- *
4
- * Maps from one type `I` to another `O[]`
5
- *
6
- * **Note:** because the output is an array you can easily support `1:1` and `1:M` mappings
7
- * but not a filtering operation (e.g., `1:0`); if you need this then use `MapToWithFiltering`
8
- * instead.
5
+ * Expresses relationship between inputs/outputs:
9
6
  */
10
- export type MapTo<I extends {}, O extends {}> = (i: I) => O[];
7
+ export enum MapDirection {
8
+ /** every input results in 0:M outputs */
9
+ OneToMany = "I -> O[]",
10
+ /** every input results in 0:1 outputs */
11
+ OneToOne = "I -> O",
12
+ /** every input is an array of type I and reduced to a single O */
13
+ ManyToOne = "I[] -> O",
14
+ }
15
+
16
+ export type MapDirectionVal = EnumValues<MapDirection>;
17
+
18
+ export type MapInput<I, IR, D extends MapDirectionVal> = D extends
19
+ | MapDirection.OneToMany
20
+ | "I -> O[]"
21
+ ? IR extends "opt"
22
+ ? I | undefined
23
+ : I
24
+ : D extends MapDirection.OneToOne | "I -> O"
25
+ ? IR extends "opt"
26
+ ? I | undefined
27
+ : I
28
+ : D extends MapDirection.ManyToOne | "I[] -> O"
29
+ ? IR extends "opt"
30
+ ? I[] | undefined
31
+ : I[]
32
+ : never;
33
+
34
+ export type MapOutput<O, OR, D extends MapDirectionVal> = D extends
35
+ | MapDirection.OneToMany
36
+ | "I -> O[]"
37
+ ? OR extends "opt"
38
+ ? O[]
39
+ : [O, ...O[]]
40
+ : D extends MapDirection.OneToOne | "I -> O"
41
+ ? OR extends "opt"
42
+ ? O | null
43
+ : O
44
+ : D extends MapDirection.ManyToOne | "I[] -> O"
45
+ ? OR extends "opt"
46
+ ? O | null
47
+ : O
48
+ : never;
11
49
 
12
50
  /**
13
- * **MapToWithFiltering**
51
+ * **MapTo<I, O>**
14
52
  *
15
- * Maps from one type `I` to another `(O | null)[]`
53
+ * A mapping function between an input type `I` and output type `O`.
16
54
  *
17
- * **Note:** because the output is an array you can easily support `1:1` and `1:M` mappings
18
- * and the allowance of the conversion to result in a `null` value also means that filter
19
- * out an input value entirely is possible. If you don't need _filtering_ then use the
20
- * `MapTo` type instead.
55
+ * **Note:** this type is designed to guide the userland mapping; refer
56
+ * to `MapFn` if you want the type output by the `mapFn()` utility.
21
57
  */
58
+ export type MapTo<
59
+ I,
60
+ O,
61
+ IR extends OptRequired = "req",
62
+ D extends MapDirectionVal = MapDirection.OneToMany,
63
+ OR extends OptRequired = "opt"
64
+ > = IR extends "opt"
65
+ ? (source?: MapInput<I, IR, D>) => MapOutput<O, OR, D>
66
+ : (source: MapInput<I, IR, D>) => MapOutput<O, OR, D>;
22
67
 
23
- export type MapToWithFiltering<I extends {}, O extends {}> = (i: I) => O[] | null;
68
+ export type MapFnOutput<I, O, S, OR extends OptRequired, D extends MapDirectionVal> = D extends
69
+ | "I -> O[]"
70
+ | MapDirection.OneToMany
71
+ ? S extends I[]
72
+ ? OR extends "opt"
73
+ ? O[]
74
+ : [O, ...O[]]
75
+ : OR extends "opt"
76
+ ? O[] | null
77
+ : O[]
78
+ : D extends MapDirection.OneToOne | "I -> O"
79
+ ? S extends I[]
80
+ ? OR extends "opt"
81
+ ? O[]
82
+ : [O, ...O[]]
83
+ : OR extends "opt"
84
+ ? O | null
85
+ : O
86
+ : D extends MapDirection.ManyToOne | "I[] -> O"
87
+ ? S extends I[][]
88
+ ? OR extends "opt"
89
+ ? O[]
90
+ : [O, ...O[]]
91
+ : OR extends "opt"
92
+ ? O | null
93
+ : O
94
+ : never;
95
+
96
+ export type MapFnInput<I, IR extends OptRequired, D extends MapDirectionVal> = D extends
97
+ | "I -> O[]"
98
+ | MapDirection.OneToMany
99
+ ? IR extends "opt"
100
+ ? I | I[] | undefined
101
+ : I | I[]
102
+ : D extends MapDirection.OneToOne | "I -> O"
103
+ ? IR extends "opt"
104
+ ? I | I[] | undefined
105
+ : I | I[]
106
+ : D extends MapDirection.ManyToOne | "I[] -> O"
107
+ ? IR extends "opt"
108
+ ? I[] | I[][] | undefined
109
+ : I[] | I[][]
110
+ : never;
111
+
112
+ /**
113
+ * The mapping function provided by the `mapFn()` utility. This _fn_
114
+ * is intended to be used in two ways:
115
+ *
116
+ * 1. Iterative:
117
+ * ```ts
118
+ * const m = mapTo<I,O>(i => [ ... ]);
119
+ * // maps inputs to outputs
120
+ * const out = inputs.map(m);
121
+ * ```
122
+ * 2. Block:
123
+ * ```ts
124
+ * // maps inputs to outputs (filtering or splitting where approp)
125
+ * const out2 = m(inputs);
126
+ * ```
127
+ */
128
+ export type MapFn<
129
+ I,
130
+ O,
131
+ IR extends OptRequired = "req",
132
+ D extends MapDirectionVal = MapDirection.OneToMany,
133
+ OR extends OptRequired = "opt"
134
+ > = IR extends "opt"
135
+ ? <S extends MapFnInput<I, IR, D>>(source?: S) => MapFnOutput<I, O, S, OR, D>
136
+ : <S extends MapFnInput<I, IR, D>>(source: S) => MapFnOutput<I, O, S, OR, D>;
@@ -37,6 +37,7 @@ export * from "./fluent/index";
37
37
  export * from "./functions/index";
38
38
  export * from "./kv/index";
39
39
  export * from "./lists/index";
40
+ export * from "./literal-unions/index";
40
41
  export * from "./string-literals/index";
41
42
  export * from "./tuples/index";
42
43
  export * from "./type-conversion/index";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Expresses whether an option is "opt" (optional) or "req" (required)
3
+ */
4
+ export type OptRequired = "opt" | "req";
@@ -0,0 +1 @@
1
+ export * from "./OptRequired";
@@ -5,9 +5,13 @@
5
5
  * // "Foo"
6
6
  * type Foo = Replace<typeof fooy, "y", "">;
7
7
  * ```
8
- *
8
+ *
9
9
  * Note: _the first match is replaced and all subsequent matches are ignored_
10
10
  */
11
- export type Replace<S extends string, W extends string, P extends string> =
12
- S extends "" ? "" : W extends "" ? S :
13
- S extends `${infer F}${W}${infer E}` ? `${F}${P}${E}` : S;
11
+ export type Replace<S extends string, W extends string, P extends string> = S extends ""
12
+ ? ""
13
+ : W extends ""
14
+ ? S
15
+ : S extends `${infer F}${W}${infer E}`
16
+ ? `${F}${P}${E}`
17
+ : S;
@@ -1,37 +1,124 @@
1
- import { MapToWithFiltering } from "src/types/dictionary";
1
+ import { MapDirectionVal, MapFn, MapFnInput, MapTo } from "src/types/dictionary";
2
+ import { OptRequired } from "src/types/literal-unions";
3
+ import { createFnWithProps } from "../createFnWithProps";
4
+
5
+ export interface MapConfig<
6
+ IR extends OptRequired = "req",
7
+ D extends MapDirectionVal = "I -> O[]",
8
+ OR extends OptRequired = "opt"
9
+ > {
10
+ input?: IR;
11
+ output?: OR;
12
+ direction?: D;
13
+ }
2
14
 
3
15
  /**
4
- * **mapTo**
5
- *
6
- * A utility function which maps one dictionary structure `I` to another `O`; which allows
7
- * the transform to:
8
- *
9
- * - _split_ inputs into multiple outputs
10
- * - _map_ 1:1 between input and output
11
- * - _filter_ inputs which don't meet certain criteria (by returning `null`)
16
+ * Provides a mapper function using the default configuration
17
+ */
18
+ export type MapperApiDefault = <I, O>(map: MapTo<I, O>) => MapFn<I, O>;
19
+
20
+ /**
21
+ * Provides opportunity to configure _input_ and _output_ relationships
22
+ * prior to providing a mapping function.
23
+ */
24
+ export type MapperApiConfigured = <
25
+ IR extends OptRequired = "req",
26
+ D extends MapDirectionVal = "I -> O[]",
27
+ OR extends OptRequired = "opt"
28
+ >(
29
+ config: MapConfig<IR, D, OR>
30
+ ) => <I, O>(map: MapTo<I, O, IR, D, OR>) => MapFn<I, O, IR, D, OR>;
31
+
32
+ /**
33
+ * The single implementation for all mapping
34
+ */
35
+ const mapper =
36
+ <
37
+ IR extends OptRequired = "req",
38
+ D extends MapDirectionVal = "I -> O[]",
39
+ OR extends OptRequired = "opt"
40
+ >(
41
+ config: MapConfig<IR, D, OR> = {}
42
+ ) =>
43
+ <I, O>(map: MapTo<I, O, IR, D, OR>) => {
44
+ // TODO
45
+ config = {
46
+ input: "req",
47
+ output: "opt",
48
+ direction: "I -> O[]",
49
+ ...config,
50
+ } as Required<MapConfig<IR, D, OR>>;
51
+
52
+ const fn = <S extends MapFnInput<I, IR, D>>(source?: S) => {
53
+ const isArray =
54
+ config.direction === "I -> O[]" && Array.isArray(source)
55
+ ? true
56
+ : config.direction === "I[] -> O" && Array.isArray(source) && Array.isArray(source[0])
57
+ ? true
58
+ : false;
59
+
60
+ if (isArray) {
61
+ return (source as any).flatMap(map);
62
+ } else {
63
+ return map(source as any);
64
+ }
65
+ };
66
+
67
+ return fn as unknown as MapFn<I, O, IR, D, OR>;
68
+ };
69
+
70
+ /**
71
+ * **mapTo** _utility_
12
72
  *
13
- * This higher order function first asks for the mapping criteria:
73
+ * This utility creates a strongly typed data mapper which maps from one
74
+ * known source `I` to another `O`:
14
75
  * ```ts
15
- * const mapper = mapTo<I,O>(i => i.name
16
- * ? [
17
- * { name: i.name, a: "static text", b: i.products.length }
18
- * ]
19
- * : null
20
- * );
76
+ * const mapper = mapTo<I, O>(i => [{
77
+ * foo: i.bar
78
+ * }]);
21
79
  * ```
80
+ */
81
+ export const mapToFn = <I, O>(map: MapTo<I, O>) => {
82
+ return mapper()<I, O>(map);
83
+ };
84
+
85
+ /**
86
+ * Provides a `config` method which allows the relationships between _inputs_
87
+ * and _outputs_ to be configured.
88
+ */
89
+ export const mapToDict = {
90
+ /** configure the relationship between the _inputs_ and _outputs_ */
91
+ config: <
92
+ IR extends OptRequired = "req",
93
+ D extends MapDirectionVal = "I -> O[]",
94
+ OR extends OptRequired = "opt"
95
+ >(
96
+ c: MapConfig<IR, D, OR> = { input: "req", output: "opt", direction: "I -> O[]" } as MapConfig<
97
+ IR,
98
+ D,
99
+ OR
100
+ >
101
+ ) => ({
102
+ /**
103
+ * create your mapping with your recently setup configuration:
104
+ * ```ts
105
+ * const mapper = mapTo
106
+ * .config({ ... })
107
+ * .map<I, O>(i => [{
108
+ * foo: i.bar
109
+ * }]);
110
+ * ```
111
+ */
112
+ map<I, O>(map: MapTo<I, O, IR, D, OR>): MapFn<I, O, IR, D, OR> {
113
+ return mapper(c)(map);
114
+ },
115
+ }),
116
+ };
117
+
118
+ /**
119
+ * **mapTo** _utility_
22
120
  *
23
- * and now you'll have a _mapper_ variable will be assigned as a mapping fn:
24
- * ```ts
25
- * function (source: I | I[]): O[]
26
- * ```
121
+ * This utility creates a strongly typed data mapper which maps from one
122
+ * known source `I` to another `O`.
27
123
  */
28
- export const mapTo =
29
- <I extends {}, O extends {}>(cb: MapToWithFiltering<I, O>) =>
30
- (source: I | I[]) => {
31
- if (Array.isArray(source)) {
32
- return source.flatMap((i) => cb(i)).filter((i) => i !== null) as O[];
33
- } else {
34
- const result = cb(source);
35
- return result ? result : ([] as O[]);
36
- }
37
- };
124
+ export const mapTo = createFnWithProps(mapToFn, mapToDict);
@@ -5,6 +5,7 @@ describe("createFnWithProps()", () => {
5
5
  it("simple fn and prop are combined, type is retained", () => {
6
6
  const fn = () => "hi";
7
7
  const props = { foo: "bar" };
8
+ /** combo */
8
9
  const combo = createFnWithProps(fn, props);
9
10
 
10
11
  expect(combo.foo).toBe("bar");
@@ -1,4 +1,4 @@
1
- import { MapTo } from "src/types/dictionary";
1
+ import { MapDirection, MapFn, MapTo } from "src/types/dictionary";
2
2
  import { describe, expect, it } from "vitest";
3
3
 
4
4
  import type { Expect, Equal } from "@type-challenges/utils";
@@ -10,115 +10,182 @@ const i2: I = { title: "i2", color: "green", products: ["foo", "bar", "baz"] };
10
10
 
11
11
  type O = { title: string; count: number; unnecessary?: number };
12
12
 
13
- describe("MapTo<T> type util", () => {
14
- it("1:1 conversion", () => {
15
- const m: MapTo<I, O> = (i) => [
16
- {
17
- title: i.title,
18
- count: i.products.length,
19
- },
13
+ describe("MapTo<I,O> and MapToFn<I,O>", () => {
14
+ it("MapTo with 0:M, 0:M cardinality", () => {
15
+ type Fn = MapTo<I, O>;
16
+ type Fn2 = MapTo<I, O, "req", MapDirection.OneToMany, "opt">;
17
+ type WFn = MapFn<I, O>;
18
+ type WFn2 = MapFn<I, O, "req", MapDirection.OneToMany, "opt">;
19
+
20
+ /** userland mapping function */
21
+ type T = (source: I) => O[];
22
+ /** exposed via mapTo() util */
23
+ type WT = <S extends I | I[]>(source: S) => S extends I[] ? O[] : O[] | null;
24
+
25
+ type cases = [
26
+ Expect<Equal<Fn, Fn2>>, // implicit default = explicit values
27
+ Expect<Equal<WFn, WFn2>>, // implicit default = explicit values
28
+ Expect<Equal<Fn, T>>,
29
+ Expect<Equal<WFn, WT>>
20
30
  ];
21
- type M = typeof m;
22
- type cases = [Expect<Equal<M, MapTo<I, O>>>];
23
- const cases: cases = [true];
24
- expect(cases).toBe(cases);
31
+ const cases: cases = [true, true, true, true];
25
32
  });
26
33
 
27
- it("1:1 conversion with non-required", () => {
28
- const m: MapTo<I, O> = (i) => [
29
- {
30
- title: i.title,
31
- count: i.products.length,
32
- unnecessary: 42,
33
- },
34
+ it("MapTo<X> with I -> O[] variable config", () => {
35
+ type Fn0 = MapTo<I, O, "req", MapDirection.OneToMany, "opt">;
36
+ type Fn1 = MapTo<I, O, "req", MapDirection.OneToMany, "req">;
37
+ type Fn2 = MapTo<I, O, "opt", MapDirection.OneToMany, "opt">;
38
+ type Fn3 = MapTo<I, O, "opt", "I -> O[]", "req">;
39
+
40
+ type T0 = (source: I) => O[];
41
+ type T1 = (source: I) => [O, ...O[]];
42
+ type T2 = (source?: I) => O[];
43
+ type T3 = (source?: I) => [O, ...O[]];
44
+
45
+ type cases = [
46
+ Expect<Equal<Fn0, T0>>, //
47
+ Expect<Equal<Fn1, T1>>, //
48
+ Expect<Equal<Fn2, T2>>, //
49
+ Expect<Equal<Fn3, T3>> //
34
50
  ];
35
- type M = typeof m;
36
- type cases = [Expect<Equal<M, MapTo<I, O>>>];
37
- const cases: cases = [true];
38
- expect(cases).toBe(cases);
51
+ const cases: cases = [true, true, true, true];
39
52
  });
40
53
 
41
- it("1:M conversion", () => {
42
- const m: MapTo<I, O> = (i) => [
43
- {
44
- title: i.title,
45
- count: i.products.length,
46
- unnecessary: 1,
47
- },
48
- {
49
- title: i.title,
50
- count: i.products.length + 1,
51
- unnecessary: 2,
52
- },
54
+ it("MapTo<X> with I[] -> O variable config", () => {
55
+ type Fn0 = MapTo<I, O, "req", "I[] -> O", "opt">;
56
+ type Fn1 = MapTo<I, O, "req", "I[] -> O", "req">;
57
+ type Fn2 = MapTo<I, O, "opt", "I[] -> O", "opt">;
58
+ type Fn3 = MapTo<I, O, "opt", "I[] -> O", "req">;
59
+
60
+ type T0 = (source: I[]) => O | null;
61
+ type T1 = (source: I[]) => O;
62
+ type T2 = (source?: I[]) => O | null;
63
+ type T3 = (source?: I[]) => O;
64
+
65
+ type cases = [
66
+ Expect<Equal<Fn0, T0>>, //
67
+ Expect<Equal<Fn1, T1>>, //
68
+ Expect<Equal<Fn2, T2>>, //
69
+ Expect<Equal<Fn3, T3>> //
53
70
  ];
54
- type M = typeof m;
55
- type cases = [Expect<Equal<M, MapTo<I, O>>>];
56
- const cases: cases = [true];
57
- expect(cases).toBe(cases);
71
+ const cases: cases = [true, true, true, true];
58
72
  });
59
73
 
60
- it("filter input as 1:0 cardinality", () => {
61
- const m: MapTo<I, O> = (_i) => [];
62
- type M = typeof m;
63
- type cases = [Expect<Equal<M, MapTo<I, O>>>];
64
- const cases: cases = [true];
65
- expect(cases).toBe(cases);
74
+ it("MapFn<X> with I -> O[] variable config", () => {
75
+ type Fn0 = MapFn<I, O, "req", "I -> O[]", "opt">;
76
+ type Fn1 = MapFn<I, O, "req", "I -> O[]", "req">;
77
+ type Fn2 = MapFn<I, O, "opt", "I -> O[]", "opt">;
78
+ type Fn3 = MapFn<I, O, "opt", "I -> O[]", "req">;
79
+
80
+ type T0 = <S extends I | I[]>(source: S) => S extends I[] ? O[] : O[] | null;
81
+ type T1 = <S extends I | I[]>(source: S) => S extends I[] ? [O, ...O[]] : O[];
82
+ type T2 = <S extends I | I[] | undefined>(source?: S) => S extends I[] ? O[] : O[] | null;
83
+ type T3 = <S extends I | I[] | undefined>(source?: S) => S extends I[] ? [O, ...O[]] : O[];
84
+
85
+ type cases = [
86
+ Expect<Equal<Fn0, T0>>, //
87
+ Expect<Equal<Fn1, T1>>, //
88
+ Expect<Equal<Fn2, T2>>, //
89
+ Expect<Equal<Fn3, T3>> //
90
+ ];
91
+ const cases: cases = [true, true, true, true];
92
+ });
93
+
94
+ it("MapFn<X> with I[] -> O variable config", () => {
95
+ type Fn0 = MapFn<I, O, "req", "I[] -> O", "opt">;
96
+ type Fn1 = MapFn<I, O, "req", "I[] -> O", "req">;
97
+ type Fn2 = MapFn<I, O, "opt", "I[] -> O", "opt">;
98
+ type Fn3 = MapFn<I, O, "opt", "I[] -> O", "req">;
99
+
100
+ type T0 = <S extends I[] | I[][]>(source: S) => S extends I[][] ? O[] : O | null;
101
+ type T1 = <S extends I[] | I[][]>(source: S) => S extends I[][] ? [O, ...O[]] : O;
102
+ type T2 = <S extends I[] | I[][] | undefined>(source?: S) => S extends I[][] ? O[] : O | null;
103
+ type T3 = <S extends I[] | I[][] | undefined>(source?: S) => S extends I[][] ? [O, ...O[]] : O;
104
+
105
+ type cases = [
106
+ Expect<Equal<Fn0, T0>>, //
107
+ Expect<Equal<Fn1, T1>>, //
108
+ Expect<Equal<Fn2, T2>>, //
109
+ Expect<Equal<Fn3, T3>> //
110
+ ];
111
+ const cases: cases = [true, true, true, true];
112
+ });
113
+
114
+ it("walk through of mapTo types", () => {
115
+ const t1 = mapTo<I, O>((i) => [{ title: i.title, count: i.products.length }]);
116
+ type T1 = typeof t1;
117
+
118
+ const a1 = mapTo
119
+ .config({ input: "req" })
120
+ .map<I, O>((i) => [{ title: i.title, count: i.products.length }]);
121
+ type A1 = typeof a1;
122
+
123
+ type cases = [
124
+ Expect<Equal<T1, MapFn<I, O>>>, //
125
+ Expect<Equal<T1, A1>>
126
+ ];
127
+ const cases: cases = [true, true];
66
128
  });
67
129
  });
68
130
 
69
131
  describe("mapTo() utility function", () => {
70
- it("test 1:1 mapping", () => {
71
- const m = mapTo<I, O>((i) => [
72
- {
73
- title: i.title,
74
- count: i.products.length,
75
- },
76
- ]);
77
- type M = typeof m;
78
- type R = ReturnType<M>;
79
-
80
- // runtime
81
- expect(m(i)).toEqual([{ title: i.title, count: i.products.length }]);
82
- // types
83
- type cases = [Expect<Equal<R, O[]>>];
84
- const cases: cases = [true];
85
- expect(cases).toBe(cases);
132
+ it("M:1 conversion", () => {
133
+ const m = mapTo
134
+ .config({
135
+ direction: "I[] -> O",
136
+ output: "req",
137
+ })
138
+ .map<I, O>((i) => ({
139
+ title: "count of products",
140
+ count: i.reduce((acc, i) => (acc = acc + i.products.length), 0),
141
+ }));
142
+
143
+ const summary = m([i, i2]);
144
+ expect(summary.title).toBe("count of products");
145
+ expect(summary.count).toBe(6);
146
+ });
147
+
148
+ it("1:1 conversion", () => {
149
+ const m = mapTo
150
+ .config({ output: "req", direction: MapDirection.OneToOne })
151
+ .map<I, O>((i) => ({ title: i.title, count: i.products.length }));
152
+ const o = m(i);
153
+
154
+ expect(o?.title).toBe(i.title);
155
+ expect(o?.count).toBe(3);
86
156
  });
87
157
 
88
- it("test 1:1 mapping with extraneous prop", () => {
89
- const m = mapTo<I, O>((i) => [
90
- {
91
- title: i.title,
92
- count: i.products.length,
93
- extraneous: 42,
94
- },
95
- ]);
96
- type M = typeof m;
97
- type R = ReturnType<M>;
98
-
99
- // runtime
100
- // TODO: extraneous props are cut out of the type but still allows us to return `O` values with extraneous props!
101
- // expect(m(i)).toEqual([{ title: i.title, count: i.products.length }]);
102
- // types
103
- type cases = [Expect<Equal<R, O[]>>];
104
- const cases: cases = [true];
105
- expect(cases).toBe(cases);
158
+ it("1:1 with 1:M conversion setting", () => {
159
+ const m = mapTo
160
+ .config({ output: "req", direction: MapDirection.OneToMany })
161
+ .map<I, O>((i) => [{ title: i.title, count: i.products.length }]);
162
+ const o = m(i);
163
+
164
+ expect(o.length).toBe(1);
165
+ expect(o[0]?.title).toBe(i.title);
166
+ expect(o[0]?.count).toBe(3);
167
+ });
168
+
169
+ it("1:M conversion ", () => {
170
+ const m = mapTo
171
+ .config({ output: "req", direction: MapDirection.OneToMany })
172
+ .map<I, O>((i) => [{ title: i.title, count: i.products.length }]);
173
+ const o = m([i, i2]);
174
+
175
+ expect(o.length).toBe(2);
176
+ o.map((item) => expect("title" in item).toBeTruthy());
106
177
  });
107
178
 
108
- it("test input filtering", () => {
109
- const m = mapTo<I, O>((x) => {
110
- return x.title === "i2"
111
- ? null
112
- : [
113
- {
114
- title: i.title,
115
- count: i.products.length,
116
- },
117
- ];
179
+ it("M:1 conversion", () => {
180
+ const m = mapTo.config({ output: "req", direction: MapDirection.ManyToOne }).map<I, O>((i) => {
181
+ return {
182
+ title: "summary",
183
+ count: i.reduce((acc, i) => (acc = acc + i.products.length), 0),
184
+ };
118
185
  });
119
- const results = m([i, i2]);
186
+ const o = m([i, i2]);
120
187
 
121
- expect(results).toHaveLength(1);
122
- expect(results[0].title).toBe("Test");
188
+ expect(o.title).toBe("summary");
189
+ expect(o.count).toBe(6);
123
190
  });
124
191
  });