inferred-types 0.23.4 → 0.24.1

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.
package/dist/index.d.ts CHANGED
@@ -667,20 +667,22 @@ declare type SpecialCharacters = "@" | "~" | "^" | "#" | "&" | "*";
667
667
  declare type NonAlpha = Whitespace | Punctuation | NumericString | Bracket | SpecialCharacters;
668
668
 
669
669
  /**
670
- * Extracts the _required_ keys in the object's type
670
+ * Extracts the _required_ keys in the object's type. You also may
671
+ * optionally filter by the _value_ of the key.
671
672
  */
672
- declare type RequiredKeys<T extends object> = {
673
+ declare type RequiredKeys<T extends object, V extends any = any> = {
673
674
  [K in keyof T]-?: {} extends {
674
675
  [P in K]: T[K];
675
- } ? never : K;
676
+ } ? never : T[K] extends V ? K : never;
676
677
  }[keyof T];
677
678
  /**
678
- * Extracts the _optional_ keys in the object's type
679
+ * Extracts the _optional_ keys in the object's type. You also may
680
+ * optionally filter by the _value_ of the key.
679
681
  */
680
- declare type OptionalKeys<T extends object> = {
682
+ declare type OptionalKeys<T extends object, V extends any = any> = {
681
683
  [K in keyof T]-?: {} extends {
682
684
  [P in K]: T[K];
683
- } ? K : never;
685
+ } ? V extends T[K] ? K : never : never;
684
686
  }[keyof T];
685
687
  /**
686
688
  * The _keys_ on a given object `T` which have a literal value of `W`.
@@ -743,7 +745,7 @@ declare type RequiredProps<T extends object> = Pick<T, RequiredKeys<T>>;
743
745
  *
744
746
  * Reduces an object to only key/value pairs where the key is optional
745
747
  */
746
- declare type OptionalProps<T extends object> = Pick<T, RequiredKeys<T>>;
748
+ declare type OptionalProps<T extends object> = Pick<T, OptionalKeys<T>>;
747
749
  /**
748
750
  * **WithValue**
749
751
  *
@@ -1320,6 +1322,8 @@ declare type FirstOfEach<T extends readonly any[][]> = T[number][0] extends T[nu
1320
1322
  */
1321
1323
  declare type TupleToUnion<T> = Mutable<T> extends any[] ? Mutable<T>[number] : never;
1322
1324
 
1325
+ declare type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
1326
+
1323
1327
  /**
1324
1328
  * Typescript utility which receives `T` as shape which resembles `DictArray<D>`
1325
1329
  * and if the type `D` can be inferred it is returned.
@@ -1380,7 +1384,9 @@ declare const api: <N extends Narrowable, TPrivate extends Readonly<Record<any,
1380
1384
  * Passing in an array of strings, you are passed back a dictionary with
1381
1385
  * all the keys being the strings and values set to `true`.
1382
1386
  * ```ts
1383
- * // { foo: true, bar: true }
1387
+ * // { bar: true, bar: true } as const;
1388
+ * const d - dictArr(arr);
1389
+ *
1384
1390
  * const fooBar = arrayToKeyLookup("foo", "bar");
1385
1391
  * ```
1386
1392
  */
@@ -1408,6 +1414,29 @@ declare function defineProperties<T extends {}>(obj: T): DefinePropertiesApi<T>;
1408
1414
  */
1409
1415
  declare function dictionaryTransform<I extends object, O extends SameKeys<I>>(input: I, transform: Transformer<I, O>): O;
1410
1416
 
1417
+ interface Uniqueness<T> {
1418
+ /** boolean flag to indicate whether the property was unique across all records */
1419
+ isUnique: boolean;
1420
+ /** the overall number of records which contained the property */
1421
+ size: number;
1422
+ /** specifies if undefined values were encountered for this property */
1423
+ includedUndefined: boolean;
1424
+ /** the unique values for the property across all records */
1425
+ values: readonly T[];
1426
+ }
1427
+ declare type DictArrApi<T extends Record<string, Narrowable>, A extends readonly T[]> = {
1428
+ length: number;
1429
+ toLookup<PL extends RequiredKeys<T, string> & keyof T & string>(prop: PL): UniqueForProp<A, PL> extends string ? Record<UniqueForProp<A, PL>, T> : Record<string, T>;
1430
+ sum<PS extends RequiredKeys<T, number> | OptionalKeys<T, number>>(prop: PS): number;
1431
+ count<PC extends OptionalKeys<T>>(prop: PC): number;
1432
+ unique<PU extends Keys<T> & keyof T>(prop: PU): Uniqueness<T[PU]>;
1433
+ };
1434
+ /**
1435
+ * converts an array of objects to a dictionary with keys formed from a given property
1436
+ * of the object and the value being the object itself.
1437
+ */
1438
+ declare const dictArr: <T extends Record<string, Narrowable>>(...dicts: readonly T[]) => DictArrApi<T, readonly T[]>;
1439
+
1411
1440
  /**
1412
1441
  * **entries**
1413
1442
  *
@@ -1741,4 +1770,4 @@ interface IFluentConfigurator<C> {
1741
1770
  */
1742
1771
  declare function FluentConfigurator<I>(initial?: I): IFluentConfigurator<{}>;
1743
1772
 
1744
- export { AllCaps, Alpha, AlphaNumeric, Api, ApiFunction, ApiValue, AppendToDictionary, AppendToObject, ArrConcat, Array$1 as Array, ArrayConverter, AssertExtends, Bracket, Break, CamelCase, CapitalizeWords, ClosingBracket, Condition, Configurator, Constructor, DashToSnake, DashUppercase, Dasherize, DefinePropertiesApi, DictArray, DictArrayFilterCallback, DictArrayKv, DictChangeValue, DictFromKv, DictKvTuple, DictPartialApplication, DictPrependWithFn, DictReturnValues, DynamicRule, DynamicRuleSet, Email, EnumValues, ExpandRecursively, ExpectExtends, ExplicitFunction, ExtendsClause, ExtendsNarrowlyClause, Filter, FinalReturn, First, FirstKey, FirstKeyValue, FirstOfEach, FluentConfigurator, FluentFunction, FromDictArray, FunctionType, GeneralDictionary, Get, HasUppercase, IConfigurator, IFluentConfigurator, If, IfExtends, IfExtendsThen, Include, Includes, IsArray, IsBoolean, IsCapitalized, IsFalse, IsFunction, IsObject, IsString, IsTrue, KebabCase, KeyValue, KeyedRecord, Keys, KeysWithValue, KvFrom, KvTuple, LastInUnion, LeftWhitespace, Length, LowerAllCaps, LowerAlpha, MapTo, MaybeFalse, MaybeTrue, Model, Mutable, MutableProps, MutationFunction, MutationIdentity, Narrowable, NonAlpha, NonNumericKeys, NonStringKeys, Not, Numeric, 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, StringKeys, StringLength, ToFluent, Transformer, Trim, TrimLeft, TrimRight, TupleToUnion, Type, TypeApi, TypeCondition, TypeDefinition, TypeGuard, TypeOptions, UnionToIntersection, UnionToTuple, UniqueDictionary, UniqueForProp, UpperAlpha, ValueTuple, ValueTypeFunc, ValueTypes, Where, WhereNot, Whitespace, WithNumericKeys, WithStringKeys, WithValue, ZipCode, and, api, arrayToKeyLookup, arrayToObject, condition, createFnWithProps, createMutationFunction, defineProperties, defineType, dictToKv, dictionaryTransform, entries, equals, filterDictArray, fnWithProps, greater, groupBy, idLiteral, idTypeGuard, identity, ifTypeOf, isArray, isBoolean, isFalse, isFunction, isNull, isNumber, isObject, isString, isSymbol, isTrue, isType, isUndefined, keys, kindLiteral, kv, kvToDict, less, literal, mapTo, mapValues, nameLiteral, or, randomString, readonlyFnWithProps, ruleSet, strArrayToDict, type, typeApi, uuid, valueTypes, withValue };
1773
+ export { AllCaps, Alpha, AlphaNumeric, Api, ApiFunction, ApiValue, AppendToDictionary, AppendToObject, ArrConcat, Array$1 as Array, ArrayConverter, AssertExtends, Bracket, Break, CamelCase, CapitalizeWords, ClosingBracket, Condition, 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, Filter, FinalReturn, First, FirstKey, FirstKeyValue, FirstOfEach, FluentConfigurator, FluentFunction, FromDictArray, FunctionType, GeneralDictionary, Get, HasUppercase, IConfigurator, IFluentConfigurator, If, IfExtends, IfExtendsThen, Include, Includes, IsArray, IsBoolean, IsCapitalized, IsFalse, IsFunction, IsObject, IsString, IsTrue, KebabCase, KeyValue, KeyedRecord, Keys, KeysWithValue, KvFrom, KvTuple, LastInUnion, LeftWhitespace, Length, LowerAllCaps, LowerAlpha, MapTo, MaybeFalse, MaybeTrue, Model, Mutable, MutableProps, MutationFunction, MutationIdentity, Narrowable, NonAlpha, NonNumericKeys, NonStringKeys, Not, Numeric, 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, StringKeys, StringLength, ToFluent, Transformer, Trim, TrimLeft, TrimRight, TupleToUnion, Type, TypeApi, TypeCondition, TypeDefinition, TypeGuard, TypeOptions, UnionToIntersection, UnionToTuple, UniqueDictionary, UniqueForProp, Uniqueness, UpperAlpha, ValueTuple, ValueTypeFunc, ValueTypes, Where, WhereNot, Whitespace, Widen, WithNumericKeys, WithStringKeys, WithValue, ZipCode, and, api, arrayToKeyLookup, arrayToObject, condition, createFnWithProps, createMutationFunction, defineProperties, defineType, dictArr, dictToKv, dictionaryTransform, entries, equals, filterDictArray, fnWithProps, greater, groupBy, idLiteral, idTypeGuard, identity, ifTypeOf, isArray, isBoolean, isFalse, isFunction, isNull, isNumber, isObject, isString, isSymbol, isTrue, isType, isUndefined, keys, kindLiteral, kv, kvToDict, less, literal, mapTo, mapValues, nameLiteral, or, randomString, readonlyFnWithProps, ruleSet, strArrayToDict, type, typeApi, uuid, valueTypes, withValue };
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ __export(src_exports, {
34
34
  createMutationFunction: () => createMutationFunction,
35
35
  defineProperties: () => defineProperties,
36
36
  defineType: () => defineType,
37
+ dictArr: () => dictArr,
37
38
  dictToKv: () => dictToKv,
38
39
  dictionaryTransform: () => dictionaryTransform,
39
40
  entries: () => entries,
@@ -203,6 +204,44 @@ function dictionaryTransform(input, transform) {
203
204
  }, {});
204
205
  }
205
206
 
207
+ // src/utility/dictionary/dictArr.ts
208
+ var dictArr = (...dicts) => {
209
+ const api2 = {
210
+ length: dicts.length,
211
+ toLookup: (prop) => {
212
+ let dict = {};
213
+ for (const obj of dicts) {
214
+ const key = obj[prop];
215
+ dict = { ...dict, [key]: obj };
216
+ }
217
+ return dict;
218
+ },
219
+ unique: (prop) => {
220
+ const v = /* @__PURE__ */ new Set();
221
+ dicts.forEach((i) => v.add(i[prop]));
222
+ const size = dictArr(...dicts).count(prop);
223
+ const values = Array.from(v);
224
+ const u = {
225
+ isUnique: values.includes(void 0) ? size === v.size - 1 : size === v.size,
226
+ includedUndefined: values.includes(void 0) ? true : false,
227
+ size,
228
+ values
229
+ };
230
+ return u;
231
+ },
232
+ sum: (prop) => {
233
+ return dicts.reduce((acc, obj) => prop in obj ? acc + obj[prop] : acc, 0);
234
+ },
235
+ count: (prop) => {
236
+ return dicts.reduce(
237
+ (acc, obj) => prop in obj && typeof obj[prop] !== "undefined" ? acc + 1 : acc,
238
+ 0
239
+ );
240
+ }
241
+ };
242
+ return api2;
243
+ };
244
+
206
245
  // src/utility/dictionary/entries.ts
207
246
  function entries(obj) {
208
247
  const iterable = {
@@ -293,9 +332,9 @@ function FluentConfigurator(initial = {}) {
293
332
  }
294
333
 
295
334
  // src/utility/dictionary/kv/filterDictArray.ts
296
- function filterDictArray(dictArr, cb) {
335
+ function filterDictArray(dictArr2, cb) {
297
336
  const state = Configurator();
298
- const updated = dictArr.filter((i) => {
337
+ const updated = dictArr2.filter((i) => {
299
338
  const [k, v] = i;
300
339
  const keep = cb(k, v);
301
340
  if (!keep) {
@@ -639,6 +678,7 @@ function withValue(td) {
639
678
  createMutationFunction,
640
679
  defineProperties,
641
680
  defineType,
681
+ dictArr,
642
682
  dictToKv,
643
683
  dictionaryTransform,
644
684
  entries,
package/dist/index.mjs CHANGED
@@ -121,6 +121,44 @@ function dictionaryTransform(input, transform) {
121
121
  }, {});
122
122
  }
123
123
 
124
+ // src/utility/dictionary/dictArr.ts
125
+ var dictArr = (...dicts) => {
126
+ const api2 = {
127
+ length: dicts.length,
128
+ toLookup: (prop) => {
129
+ let dict = {};
130
+ for (const obj of dicts) {
131
+ const key = obj[prop];
132
+ dict = { ...dict, [key]: obj };
133
+ }
134
+ return dict;
135
+ },
136
+ unique: (prop) => {
137
+ const v = /* @__PURE__ */ new Set();
138
+ dicts.forEach((i) => v.add(i[prop]));
139
+ const size = dictArr(...dicts).count(prop);
140
+ const values = Array.from(v);
141
+ const u = {
142
+ isUnique: values.includes(void 0) ? size === v.size - 1 : size === v.size,
143
+ includedUndefined: values.includes(void 0) ? true : false,
144
+ size,
145
+ values
146
+ };
147
+ return u;
148
+ },
149
+ sum: (prop) => {
150
+ return dicts.reduce((acc, obj) => prop in obj ? acc + obj[prop] : acc, 0);
151
+ },
152
+ count: (prop) => {
153
+ return dicts.reduce(
154
+ (acc, obj) => prop in obj && typeof obj[prop] !== "undefined" ? acc + 1 : acc,
155
+ 0
156
+ );
157
+ }
158
+ };
159
+ return api2;
160
+ };
161
+
124
162
  // src/utility/dictionary/entries.ts
125
163
  function entries(obj) {
126
164
  const iterable = {
@@ -211,9 +249,9 @@ function FluentConfigurator(initial = {}) {
211
249
  }
212
250
 
213
251
  // src/utility/dictionary/kv/filterDictArray.ts
214
- function filterDictArray(dictArr, cb) {
252
+ function filterDictArray(dictArr2, cb) {
215
253
  const state = Configurator();
216
- const updated = dictArr.filter((i) => {
254
+ const updated = dictArr2.filter((i) => {
217
255
  const [k, v] = i;
218
256
  const keep = cb(k, v);
219
257
  if (!keep) {
@@ -556,6 +594,7 @@ export {
556
594
  createMutationFunction,
557
595
  defineProperties,
558
596
  defineType,
597
+ dictArr,
559
598
  dictToKv,
560
599
  dictionaryTransform,
561
600
  entries,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inferred-types",
3
- "version": "0.23.4",
3
+ "version": "0.24.1",
4
4
  "description": "Functions which provide useful type inference on TS projects",
5
5
  "license": "MIT",
6
6
  "author": "Ken Snyder<ken@ken.net>",
@@ -1,17 +1,27 @@
1
1
  import { Alpha } from "../alphabetic/alpha-characters";
2
2
 
3
3
  /**
4
- * Extracts the _required_ keys in the object's type
4
+ * Extracts the _required_ keys in the object's type. You also may
5
+ * optionally filter by the _value_ of the key.
5
6
  */
6
- export type RequiredKeys<T extends object> = {
7
- [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K;
7
+ export type RequiredKeys<T extends object, V extends any = any> = {
8
+ [K in keyof T]-?: {} extends { [P in K]: T[K] }
9
+ ? never //
10
+ : T[K] extends V
11
+ ? K
12
+ : never;
8
13
  }[keyof T];
9
14
 
10
15
  /**
11
- * Extracts the _optional_ keys in the object's type
16
+ * Extracts the _optional_ keys in the object's type. You also may
17
+ * optionally filter by the _value_ of the key.
12
18
  */
13
- export type OptionalKeys<T extends object> = {
14
- [K in keyof T]-?: {} extends { [P in K]: T[K] } ? K : never;
19
+ export type OptionalKeys<T extends object, V extends any = any> = {
20
+ [K in keyof T]-?: {} extends { [P in K]: T[K] }
21
+ ? V extends T[K]
22
+ ? K
23
+ : never //
24
+ : never;
15
25
  }[keyof T];
16
26
 
17
27
  /**
@@ -84,7 +94,7 @@ export type RequiredProps<T extends object> = Pick<T, RequiredKeys<T>>;
84
94
  *
85
95
  * Reduces an object to only key/value pairs where the key is optional
86
96
  */
87
- export type OptionalProps<T extends object> = Pick<T, RequiredKeys<T>>;
97
+ export type OptionalProps<T extends object> = Pick<T, OptionalKeys<T>>;
88
98
 
89
99
  /**
90
100
  * **WithValue**
@@ -0,0 +1,7 @@
1
+ export type Widen<T> = T extends string
2
+ ? string
3
+ : T extends number
4
+ ? number
5
+ : T extends boolean
6
+ ? boolean
7
+ : T;
@@ -8,6 +8,7 @@
8
8
  export * from "./TupleToUnion";
9
9
  export * from "./UnionToIntersection";
10
10
  export * from "./UnionToTuple";
11
+ export * from "./Widen";
11
12
 
12
13
  // #endregion auto-indexed files
13
14
 
@@ -2,7 +2,9 @@
2
2
  * Passing in an array of strings, you are passed back a dictionary with
3
3
  * all the keys being the strings and values set to `true`.
4
4
  * ```ts
5
- * // { foo: true, bar: true }
5
+ * // { bar: true, bar: true } as const;
6
+ * const d - dictArr(arr);
7
+ *
6
8
  * const fooBar = arrayToKeyLookup("foo", "bar");
7
9
  * ```
8
10
  */
@@ -0,0 +1,69 @@
1
+ import { Keys, Narrowable, OptionalKeys, RequiredKeys, UniqueForProp } from "src/types";
2
+
3
+ export interface Uniqueness<T> {
4
+ /** boolean flag to indicate whether the property was unique across all records */
5
+ isUnique: boolean;
6
+ /** the overall number of records which contained the property */
7
+ size: number;
8
+ /** specifies if undefined values were encountered for this property */
9
+ includedUndefined: boolean;
10
+ /** the unique values for the property across all records */
11
+ values: readonly T[];
12
+ }
13
+
14
+ export type DictArrApi<T extends Record<string, Narrowable>, A extends readonly T[]> = {
15
+ length: number;
16
+ toLookup<PL extends RequiredKeys<T, string> & keyof T & string>(
17
+ prop: PL
18
+ ): UniqueForProp<A, PL> extends string ? Record<UniqueForProp<A, PL>, T> : Record<string, T>;
19
+ sum<PS extends RequiredKeys<T, number> | OptionalKeys<T, number>>(prop: PS): number;
20
+ count<PC extends OptionalKeys<T>>(prop: PC): number;
21
+ unique<PU extends Keys<T> & keyof T>(prop: PU): Uniqueness<T[PU]>;
22
+ };
23
+
24
+ /**
25
+ * converts an array of objects to a dictionary with keys formed from a given property
26
+ * of the object and the value being the object itself.
27
+ */
28
+ export const dictArr = <T extends Record<string, Narrowable>>(...dicts: readonly T[]) => {
29
+ // build API
30
+ const api: DictArrApi<T, typeof dicts> = {
31
+ length: dicts.length,
32
+ toLookup: (prop) => {
33
+ let dict: Record<string, T> = {};
34
+ for (const obj of dicts) {
35
+ const key = obj[prop] as string;
36
+ dict = { ...dict, [key]: obj };
37
+ }
38
+ return dict;
39
+ },
40
+ /**
41
+ * The unique values of a given property
42
+ */
43
+ unique: (prop) => {
44
+ type P = typeof prop;
45
+ const v = new Set<T[P]>();
46
+ dicts.forEach((i) => v.add(i[prop]));
47
+ const size = dictArr(...dicts).count(prop as any);
48
+ const values = Array.from(v);
49
+ const u: Uniqueness<T[P]> = {
50
+ isUnique: values.includes(undefined as any) ? size === v.size - 1 : size === v.size,
51
+ includedUndefined: values.includes(undefined as any) ? true : false,
52
+ size,
53
+ values,
54
+ };
55
+ return u;
56
+ },
57
+ sum: (prop) => {
58
+ return dicts.reduce((acc, obj) => (prop in obj ? acc + (obj[prop] as number) : acc), 0);
59
+ },
60
+ count: (prop) => {
61
+ return dicts.reduce(
62
+ (acc, obj) => (prop in obj && typeof obj[prop] !== "undefined" ? acc + 1 : acc),
63
+ 0
64
+ );
65
+ },
66
+ };
67
+
68
+ return api;
69
+ };
@@ -7,6 +7,7 @@
7
7
  export * from "./arrayToKeyLookup";
8
8
  export * from "./defineProperties";
9
9
  export * from "./dictionaryTransform";
10
+ export * from "./dictArr";
10
11
  export * from "./entries";
11
12
  export * from "./mapValues";
12
13
  export * from "./mapTo";
@@ -0,0 +1,24 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import type { Expect, Equal } from "@type-challenges/utils";
3
+ import { OptionalKeys } from "src/types/dictionary";
4
+
5
+ type Test = { title: string; value: number; color?: string };
6
+
7
+ describe("OptionalKeys<T, V>", () => {
8
+ it("basic usage without filtering on value", () => {
9
+ type T = OptionalKeys<Test>;
10
+
11
+ type cases = [Expect<Equal<T, "color">>];
12
+ const cases: cases = [true];
13
+ expect(cases).toBe(cases);
14
+ });
15
+
16
+ it("basic usage with a value filter", () => {
17
+ type T1 = OptionalKeys<Test, string>;
18
+ type T2 = OptionalKeys<Test, number>;
19
+
20
+ type cases = [Expect<Equal<T1, "color">>, Expect<Equal<T2, never>>];
21
+ const cases: cases = [true, true];
22
+ expect(cases).toBe(cases);
23
+ });
24
+ });
@@ -0,0 +1,53 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import type { Expect, Equal } from "@type-challenges/utils";
3
+ import { RequiredKeys } from "src/types/dictionary";
4
+ import { First, Narrowable } from "src";
5
+
6
+ type Test = { title: string; value: number; color?: string };
7
+
8
+ describe("RequiredKeys<T, V>", () => {
9
+ it("basic usage without filtering on value", () => {
10
+ type T = RequiredKeys<Test>;
11
+
12
+ type cases = [Expect<Equal<T, "title" | "value">>];
13
+ const cases: cases = [true];
14
+ expect(cases).toBe(cases);
15
+ });
16
+
17
+ it("basic usage with a value filter", () => {
18
+ type T1 = RequiredKeys<Test, string>;
19
+ type T2 = RequiredKeys<Test, number>;
20
+
21
+ type cases = [Expect<Equal<T1, "title">>, Expect<Equal<T2, "value">>];
22
+ const cases: cases = [true, true];
23
+ expect(cases).toBe(cases);
24
+ });
25
+
26
+ it("typed explicitly", () => {
27
+ type O = {
28
+ id: number;
29
+ name: string;
30
+ title: string;
31
+ cost?: number;
32
+ color?: string;
33
+ };
34
+
35
+ const genericArr: O[] = [
36
+ { id: 1, name: "foo", title: "one", cost: 15 },
37
+ { id: 2, name: "bar", title: "one" },
38
+ { id: 3, name: "baz", title: "two", cost: 45, color: "green" },
39
+ ];
40
+ const fn = <T extends Record<string, Narrowable>, A extends readonly T[]>(arr: A): A => arr;
41
+
42
+ const v = fn(genericArr);
43
+ type V = typeof v;
44
+ type T1 = RequiredKeys<First<V>>;
45
+ type T2 = RequiredKeys<First<V>, string>;
46
+
47
+ type cases = [
48
+ Expect<Equal<T1, "id" | "name" | "title">>, //
49
+ Expect<Equal<T2, "name" | "title">>
50
+ ];
51
+ const cases: cases = [true, true];
52
+ });
53
+ });
@@ -1,26 +1,59 @@
1
1
  import { describe, it, expect } from "vitest";
2
-
3
- import { Expect, Equal, ExpectExtends } from "@type-challenges/utils";
2
+ import { Expect, Equal } from "@type-challenges/utils";
4
3
  import { UniqueForProp } from "src/types/lists";
5
4
 
5
+ const narrow_data = [
6
+ { id: 123, color: "blue" },
7
+ { id: 456, color: "red" },
8
+ ] as const;
9
+ type NarrowData = typeof narrow_data;
10
+
11
+ type R = { id: number; color: string };
12
+ const wide_data: readonly R[] = [
13
+ { id: 123, color: "blue" },
14
+ { id: 456, color: "red" },
15
+ ];
16
+ type WideData = typeof wide_data;
17
+
18
+ type R2<T extends string> = Readonly<{ id: number; color: T }>;
19
+ const hybrid = <H extends R2<any>>(...data: H[]): readonly H[] => data;
20
+ const hybrid_data = hybrid(
21
+ ...[
22
+ { id: 123, color: "blue" },
23
+ { id: 456, color: "red" },
24
+ ]
25
+ );
26
+ type HybridData = typeof hybrid_data;
27
+
6
28
  describe("UniqueForProp<T, P>", () => {
7
- it("pull off uniques for 'id' prop", () => {
8
- const data = [
9
- { id: 123, color: "blue" },
10
- { id: 456, color: "red" },
11
- ] as const;
12
- type Data = typeof data;
13
- type U = UniqueForProp<Data, "id">;
29
+ it("narrow data works as expected", () => {
30
+ type U = UniqueForProp<NarrowData, "id">;
14
31
 
15
32
  type cases = [
16
33
  // the expected keys are part of the union
17
- Expect<ExpectExtends<U, 123>>,
18
- Expect<ExpectExtends<U, 456>>,
19
- // but so is a lot other junk
20
34
  Expect<Equal<U, 123 | 456>>
21
35
  ];
22
36
 
23
- const c: cases = [true, true, true];
37
+ const c: cases = [true];
38
+ expect(c).toBe(c);
39
+ });
40
+
41
+ it("wide data only knows the wide type", () => {
42
+ type U = UniqueForProp<WideData, "id">;
43
+
44
+ type cases = [
45
+ // the keys are rolled up the wide type
46
+ Expect<Equal<U, number>>
47
+ ];
48
+
49
+ const c: cases = [true];
50
+ expect(c).toBe(c);
51
+ });
52
+
53
+ it("wide data only knows the wide type", () => {
54
+ type U = UniqueForProp<HybridData, "id">;
55
+ type cases = [Expect<Equal<U, number>>];
56
+ const c: cases = [true];
24
57
  expect(c).toBe(c);
25
58
  });
26
59
  });
@@ -0,0 +1,110 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { Expect, Equal } from "@type-challenges/utils";
3
+ import { dictArr, keys, literal } from "src/utility";
4
+
5
+ type O = {
6
+ id: number;
7
+ name: string;
8
+ title: string;
9
+ cost?: number;
10
+ color?: string;
11
+ };
12
+
13
+ const genericArr: O[] = [
14
+ { id: 1, name: "foo", title: "one", cost: 15 },
15
+ { id: 2, name: "bar", title: "one" },
16
+ { id: 3, name: "baz", title: "two", cost: 45, color: "green" },
17
+ ];
18
+
19
+ const literalArr = [
20
+ literal({ id: 1, name: "foo", title: "one", cost: 15 }),
21
+ literal({ id: 2, name: "bar", title: "one" }),
22
+ literal({ id: 3, name: "baz", title: "two", cost: 45, color: "green" }),
23
+ ];
24
+
25
+ const wideArr = [
26
+ { id: 1, name: "foo", title: "one", cost: 15 },
27
+ { id: 2, name: "bar", title: "one" },
28
+ { id: 3, name: "baz", title: "two", cost: 45, color: "green" },
29
+ ];
30
+
31
+ describe("dictArr() utility API", () => {
32
+ it("returns an API when passed the array", () => {
33
+ const r = dictArr(...genericArr);
34
+ type R = typeof r;
35
+ // runtime checks
36
+ expect(typeof r.toLookup).contains("function");
37
+ expect(typeof r.unique).contains("function");
38
+ expect(typeof r.count).contains("function");
39
+ expect(typeof r.sum).contains("function");
40
+ expect(typeof r.length).contains("number");
41
+
42
+ // type checks
43
+ type ToLookupParam = Parameters<R["toLookup"]>[0];
44
+ type UniqueParam = Parameters<R["unique"]>[0];
45
+ type CountParam = Parameters<R["count"]>[0];
46
+ type SumParam = Parameters<R["sum"]>[0];
47
+
48
+ // type ToLookupReturn = ReturnType<R["toLookup"]>;
49
+
50
+ type cases = [
51
+ Expect<Equal<ToLookupParam, "name" | "title">>,
52
+ Expect<Equal<UniqueParam, "id" | "name" | "title" | "cost" | "color">>,
53
+ Expect<Equal<CountParam, "cost" | "color">>,
54
+ Expect<Equal<SumParam, "id" | "cost">>
55
+ ];
56
+ const cases: cases = [true, true, true, true];
57
+ });
58
+
59
+ it("toLookup() creates type-strong lookup", () => {
60
+ const g = dictArr(...genericArr);
61
+ const l = dictArr(...literalArr);
62
+ const pl = dictArr(...wideArr);
63
+ const byNameG = g.toLookup("name");
64
+ const byNameL = l.toLookup("name");
65
+ const byNamePL = pl.toLookup("name");
66
+ const kg = keys(byNameG);
67
+ const kl = keys(byNameL);
68
+ const kpl = keys(byNamePL);
69
+ expect(kg).contains("foo");
70
+ expect(kg).contains("bar");
71
+ expect(kg).contains("baz");
72
+
73
+ expect(kl).contains("foo");
74
+ expect(kl).contains("bar");
75
+ expect(kl).contains("baz");
76
+
77
+ expect(kpl).contains("foo");
78
+ expect(kpl).contains("bar");
79
+ expect(kpl).contains("baz");
80
+
81
+ const fooG = genericArr.find((i) => i.name === "foo");
82
+ expect(byNameG.foo).toEqual(fooG);
83
+
84
+ const fooL = literalArr.find((i) => i.name === "foo");
85
+ expect(byNameL.foo).toEqual(fooL);
86
+ });
87
+
88
+ it("summarization works", () => {
89
+ const r = dictArr(...genericArr);
90
+ expect(r.sum("cost")).toBe(60);
91
+ });
92
+
93
+ it("counting of non-required props works", () => {
94
+ const r = dictArr(...genericArr);
95
+ expect(r.count("color")).toBe(1);
96
+ expect(r.count("cost")).toBe(2);
97
+ });
98
+
99
+ it("uniqueness", () => {
100
+ const r = dictArr(...genericArr);
101
+ const color = r.unique("color");
102
+ const title = r.unique("title");
103
+
104
+ expect(color.isUnique).toBeTruthy();
105
+ expect(color.includedUndefined).toBeTruthy();
106
+ expect(color.values).toEqual([undefined, "green"]);
107
+ expect(title.isUnique).toBeFalsy();
108
+ expect(title.values).toEqual(["one", "two"]);
109
+ });
110
+ });