inferred-types 0.19.0 → 0.22.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 (133) hide show
  1. package/.eslintrc +36 -20
  2. package/.github/workflows/main.yml +34 -0
  3. package/.github/workflows/other.yml +15 -0
  4. package/.vscode/settings.json +10 -2
  5. package/dist/index.d.ts +403 -8538
  6. package/dist/index.js +35 -60
  7. package/dist/index.mjs +24 -48
  8. package/package.json +34 -23
  9. package/src/shared/valueTypes.ts +1 -1
  10. package/src/types/ExpandRecursively.ts +10 -1
  11. package/src/types/FunctionType.ts +1 -1
  12. package/src/types/SimplifyObject.ts +12 -0
  13. package/src/types/Transformer.ts +1 -1
  14. package/src/types/alphabetic/CamelCase.ts +1 -1
  15. package/src/types/alphabetic/CapFirstAlpha.ts +7 -3
  16. package/src/types/alphabetic/Dasherize.ts +8 -5
  17. package/src/types/alphabetic/KebabCase.ts +1 -1
  18. package/src/types/alphabetic/PascalCase.ts +3 -1
  19. package/src/types/alphabetic/SnakeCase.ts +8 -3
  20. package/src/types/dictionary/DictPartialApplication.ts +30 -0
  21. package/src/types/dictionary/DictPrependWithFn.ts +23 -0
  22. package/src/types/dictionary/DictReturnValues.ts +27 -0
  23. package/src/types/dictionary/RequireProps.ts +15 -0
  24. package/src/types/{type-conversion → dictionary}/SameKeys.ts +0 -0
  25. package/src/types/dictionary/index.ts +8 -3
  26. package/src/types/{props.ts → dictionary/props.ts} +1 -1
  27. package/src/types/functions/FinalReturn.ts +12 -0
  28. package/{on-hold/types → src/types/functions}/index.ts +4 -3
  29. package/src/types/index.ts +4 -3
  30. package/src/types/ruleset-types.ts +3 -3
  31. package/src/types/runtime.ts +18 -18
  32. package/src/types/string-literals/LeftWhitespace.ts +5 -2
  33. package/src/types/string-literals/RightWhitespace.ts +5 -2
  34. package/src/types/tuples/FirstKey.ts +3 -3
  35. package/src/types/tuples/FromDictArray.ts +5 -4
  36. package/src/types/type-conversion/index.ts +2 -5
  37. package/src/utility/api/api.ts +8 -7
  38. package/src/utility/createFnWithProps.ts +26 -1
  39. package/src/utility/dictionary/dictionaryTransform.ts +5 -4
  40. package/src/utility/dictionary/entries.ts +9 -5
  41. package/src/utility/dictionary/index.ts +2 -3
  42. package/src/utility/dictionary/kv/dictToKv.ts +5 -3
  43. package/src/utility/dictionary/kv/filterDictArray.ts +5 -7
  44. package/src/utility/dictionary/kv/kv.ts +8 -3
  45. package/src/utility/dictionary/kv/kvToDict.ts +3 -2
  46. package/src/utility/dictionary/mapValues.ts +8 -5
  47. package/src/utility/dictionary/strArrayToDict.ts +1 -2
  48. package/src/utility/keys.ts +8 -4
  49. package/src/utility/lists/groupBy.ts +2 -9
  50. package/src/utility/literals/arrayToObject.ts +29 -29
  51. package/src/utility/literals/defineType.ts +6 -6
  52. package/src/utility/literals/identity.ts +5 -2
  53. package/src/utility/literals/literal.ts +3 -3
  54. package/src/utility/ruleset.ts +11 -9
  55. package/src/utility/runtime/condition.ts +1 -1
  56. package/src/utility/runtime/conditions/isFalse.ts +2 -2
  57. package/src/utility/runtime/conditions/isFunction.ts +2 -2
  58. package/src/utility/runtime/conditions/isObject.ts +4 -1
  59. package/src/utility/runtime/conditions/isTrue.ts +1 -1
  60. package/src/utility/runtime/ifTypeOf.ts +1 -1
  61. package/src/utility/runtime/type.ts +3 -1
  62. package/src/utility/runtime/withValue.ts +5 -3
  63. package/src/utility/state/Configurator.ts +2 -2
  64. package/src/utility/state/FluentConfigurator.ts +2 -2
  65. package/src/utility/state/index.ts +2 -3
  66. package/tests/CamelCase-spec.ts +2 -0
  67. package/tests/ExplicitFunction-spec.ts +3 -2
  68. package/tests/IncludeAndRetain-spec.ts +7 -12
  69. package/tests/Includes-spec.ts +2 -0
  70. package/tests/KebabCase-spec.ts +2 -0
  71. package/tests/MutationIdentity-spec.ts +2 -1
  72. package/tests/PascalCase-spec.ts +2 -0
  73. package/tests/Pluralize-spec.ts +2 -0
  74. package/tests/SnakeCase-spec.ts +2 -0
  75. package/tests/Where.spec.ts +9 -10
  76. package/tests/arrayToKeyLookup-spec.ts +5 -5
  77. package/tests/arrayToObject-spec.ts +12 -8
  78. package/tests/constructor.spec.ts +2 -0
  79. package/tests/createFnWithProps.spec.ts +2 -2
  80. package/tests/defineType-spec.ts +17 -11
  81. package/tests/dictionary/DictPartialApplication.test.ts +43 -0
  82. package/tests/dictionary/DictReturnValues.test.ts +32 -0
  83. package/tests/dictionary/Get.spec.ts +5 -3
  84. package/tests/dictionary/PrependValuesWithFunction.ts +34 -0
  85. package/tests/dictionary/RequireProps.test.ts +21 -0
  86. package/tests/dictionaryTransform-spec.ts +3 -3
  87. package/tests/ifTypeOf-spec.ts +31 -20
  88. package/tests/kv/KeyValue-spec.ts +4 -4
  89. package/tests/kv/SameKeys-spec.ts +6 -9
  90. package/tests/kv/dict-to-kv-and-back.spec.ts +5 -4
  91. package/tests/kv/entries-spec.ts +8 -9
  92. package/tests/kv/keys.spec.ts +9 -19
  93. package/tests/kv/kv-spec.ts +4 -4
  94. package/tests/lists/UniqueForProp.spec.ts +3 -1
  95. package/tests/literal-spec.ts +5 -3
  96. package/tests/mapValues-spec.ts +5 -3
  97. package/tests/props-spec.ts +3 -2
  98. package/tests/runtime/condition.spec.ts +2 -1
  99. package/tests/runtime/type.spec.ts +4 -3
  100. package/tests/strArrayToDict-spec.ts +6 -6
  101. package/tests/string-literals/AllCaps.spec.ts +6 -15
  102. package/tests/string-literals/CamelCase.spec.ts +13 -32
  103. package/tests/string-literals/Dasherize.spec.ts +13 -31
  104. package/tests/string-literals/HasUppercase.spec.ts +6 -15
  105. package/tests/string-literals/PascalCase.spec.ts +13 -32
  106. package/tests/string-literals/SnakeCase.spec.ts +13 -32
  107. package/tests/string-literals/whitespace-capture.spec.ts +10 -28
  108. package/tests/tuples/TupleToUnion.spec.ts +3 -1
  109. package/tests/withValue.spec.ts +4 -3
  110. package/tsconfig.json +8 -19
  111. package/vitest.config.ts +13 -0
  112. package/jest.config.ts +0 -20
  113. package/on-hold/Builder/Builder.ts +0 -66
  114. package/on-hold/Builder/BuilderApi.ts +0 -21
  115. package/on-hold/Builder/IdentityToMutationApi.ts +0 -44
  116. package/on-hold/Builder/MutationToFluentApi.ts +0 -60
  117. package/on-hold/Builder/index.ts +0 -35
  118. package/on-hold/tests/Builder-spec.ts +0 -310
  119. package/on-hold/tests/RuleSet-spec.ts +0 -59
  120. package/on-hold/tests/ToFluent-spec.ts +0 -60
  121. package/on-hold/tests/api-spec.ts +0 -73
  122. package/on-hold/tests/data/builder-data.ts +0 -53
  123. package/on-hold/tests/filterDictArray-spec.ts +0 -50
  124. package/on-hold/tests/run-time-spec.ts +0 -44
  125. package/on-hold/type.ts +0 -20
  126. package/on-hold/types/object.ts +0 -19
  127. package/on-hold/types/prop.ts +0 -28
  128. package/on-hold/types/scalar.ts +0 -37
  129. package/src/types/dictionary/NotEmptyObject.ts +0 -9
  130. package/src/types/type-conversion/UnwrapValue.ts +0 -13
  131. package/src/types/type-conversion/WrapValue.ts +0 -15
  132. package/src/utility/dictionary/valuesOfProp.ts +0 -13
  133. package/src/utility/state/KeyStorage.ts +0 -23
package/jest.config.ts DELETED
@@ -1,20 +0,0 @@
1
- /* eslint-disable unicorn/import-style */
2
- import type { Config } from "@jest/types";
3
- import { resolve } from "path";
4
-
5
- const config: Config.InitialOptions = {
6
- verbose: true,
7
- // roots: ["tests", "src"],
8
- transform: {
9
- "^.+\\.tsx?$": "ts-jest",
10
- },
11
- testPathIgnorePatterns: ["/node_modules/", "/on-hold/"],
12
- moduleNameMapper: {
13
- "^[/]{0,1}~/(.*)$": resolve(process.cwd(), "src", "$1"),
14
- },
15
- testMatch: ["**/?(*[-.])+(spec|test).ts"],
16
- setupFilesAfterEnv: ["jest-extended"],
17
- testEnvironment: "node",
18
- };
19
-
20
- export default config;
@@ -1,66 +0,0 @@
1
- import { TypeGuard } from "~/types";
2
-
3
- import { MutationIdentity } from "../Mutation";
4
- import { IdentityToMutationApi, MutationToFluentApi } from "./index";
5
-
6
- /* eslint-disable no-use-before-define */
7
-
8
- export type BuilderComplete<C, S> = C extends infer U ? (U extends S ? "" : "unwrap") : never;
9
-
10
- /**
11
- * **Builder**
12
- *
13
- * Creates a builder pattern that provides a user-friendly (and strongly typed)
14
- * means of configuring a defined data structure.
15
- *
16
- * The _first_ function call establishes a type-guard to validate when the desired
17
- * configuration has been achieved as well as the fluent API surface that should
18
- * be used to configure this builder.
19
- *
20
- * The _second_ function call allows for setting an initial configuration state.
21
- *
22
- * **Example:**
23
- *
24
- * ```ts
25
- * // configure
26
- * type Config = { foo: number };
27
- * const typeGuard = (thing: unknown): thing is Config { ... };
28
- * const config = Builder(typeGuard, api)();
29
- * // use (and unwrap)
30
- * const myConfig = config.set(5).add(3).unwrap();
31
- * ```
32
- */
33
- export function Builder<
34
- TState extends object,
35
- TApi extends { [key: string]: MutationIdentity<any, any> }
36
- >(validate: TypeGuard<TState>, apiDefinition: TApi) {
37
- /**
38
- * The non-fluent API component of the Builder API
39
- */
40
- type IEscape<T extends object = {}> = { unwrap: () => TState; current: Partial<TState> } & T;
41
-
42
- return <TCurrent extends Partial<TState>>(state: TCurrent) => {
43
- const escapeApi: IEscape = {
44
- current: state,
45
- unwrap: () => {
46
- if (!validate(state)) {
47
- throw new Error(
48
- `Attempt to unwrap() the state prior to it being completed! The configuration at this point is:\n${JSON.stringify(
49
- state,
50
- null,
51
- 2
52
- )}`
53
- );
54
- }
55
- return state;
56
- },
57
- };
58
-
59
- // provide state to identity functions to make them mutation functions
60
- const mutationApi = IdentityToMutationApi(state)(apiDefinition);
61
- // determine the exclusions
62
- type Complete = BuilderComplete<TCurrent, TState>;
63
- // transform mutation API to fluent API
64
- return MutationToFluentApi<TState, TApi, IEscape, Complete>(validate, apiDefinition)(mutationApi, escapeApi);
65
- };
66
- }
@@ -1,21 +0,0 @@
1
- /* eslint-disable no-use-before-define */
2
- import { MutationIdentity } from "~/Mutation";
3
-
4
- /**
5
- * Used to facilitate building **Identity APIs** to be used
6
- * in the `Builder` primitive.
7
- */
8
- export function BuilderApi<T extends object>() {
9
- return function BuilderKeyValue<
10
- // identity function (s => fn => s)
11
- V extends MutationIdentity<PT, any>,
12
- // API property
13
- K extends string,
14
- // KV pair
15
- O extends Record<K, V>,
16
- /** Provide structure of Partial<T> while using extends to preserve type info */
17
- PT extends Partial<T>
18
- >(key: K, value: V): O {
19
- return { [key as K]: value } as O;
20
- };
21
- }
@@ -1,44 +0,0 @@
1
- import { MutationIdentity } from "~/Mutation";
2
- import { iterateDict } from "~/utility";
3
-
4
- /**
5
- * Exposes a Mutation Function when provided a Mutation Identity HO function
6
- */
7
- export type MutationApi<T extends Record<string, (s: any) => (...args: any[]) => any>> = {
8
- [P in keyof T]: ReturnType<T[P]>;
9
- };
10
-
11
- /**
12
- * Converts an API surface of `MutationIdentity` functions into the same API surface
13
- * but the functions now are just mutation functions which have an enclosed reference
14
- * to the _state_ which is being managed.
15
- *
16
- * ```ts
17
- * {
18
- * foo: s => (a, b, c) => s
19
- * bar: s => () => s
20
- * }
21
- * ```
22
- *
23
- * is converted to
24
- *
25
- * ```ts
26
- * {
27
- * foo: (a, b, c) => s
28
- * bar: () => s
29
- * }
30
- * ```
31
- */
32
- export function IdentityToMutationApi<TState extends object, TCurrent extends Partial<TState>>(
33
- state: TCurrent
34
- ) {
35
- return <TApi extends { [key: string]: MutationIdentity<any, any> }>(identity: TApi) => {
36
- const api: any = {};
37
-
38
- for (const [k, v] of iterateDict(identity)) {
39
- api[k] = v(state);
40
- }
41
-
42
- return api as MutationApi<TApi>;
43
- };
44
- }
@@ -1,60 +0,0 @@
1
- /* eslint-disable unicorn/consistent-function-scoping */
2
- /* eslint-disable no-use-before-define */
3
- import { MutationIdentity } from "~/Mutation";
4
- import { FluentApi, ToFluent, TypeGuard } from "~/types";
5
- import { dictionaryTransform } from "~/utility";
6
- import { Builder } from "./Builder";
7
- import { MutationApi } from "./IdentityToMutationApi";
8
-
9
- /**
10
- * Wraps a mutation API so that when a function is called:
11
- *
12
- * 1. The mutation to state is completed, and then
13
- * 2. returns a new call to Builder to re-establish the fluent API
14
- *
15
- * Note: this also allows an `EscapeApi` to sit next to the fluent API
16
- *
17
- * This function expects an API surface similar to this:
18
- *
19
- * ```ts
20
- * {
21
- * foo: (a, b, c) => s,
22
- * bar: () => s
23
- * }
24
- * ```
25
- *
26
- * and will return:
27
- *
28
- * ```ts
29
- * {
30
- * foo: (a, b, c) => { FLUENT API },
31
- * bar: () => { FLUENT API },
32
- * unwrap: () => { STATE }
33
- * }
34
- * ```
35
- */
36
- export function MutationToFluentApi<
37
- TState extends object,
38
- TApi extends { [key: string]: MutationIdentity<any, any> },
39
- TEscapeApi extends { [E in keyof TEscapeApi]: any },
40
- TExclude extends string
41
- >(validate: TypeGuard<TState>, apiDefinition: TApi) {
42
- // receives the two API's and merges them
43
- return (mutationApi: MutationApi<TApi>, escapeApi: TEscapeApi) => {
44
- // mutate internal state and then return fluent API
45
- type Api = FluentApi<ToFluent<MutationApi<TApi>, TEscapeApi>, TEscapeApi, any>;
46
- const fluent = dictionaryTransform<MutationApi<TApi>, Api>(mutationApi, (i, k) => {
47
- const fn = i[k];
48
- return <TNewState extends Partial<TState>>(...args: Parameters<typeof fn>) => {
49
- const newState = fn(args) as TNewState;
50
-
51
- // recurse back to Builder
52
- return validate(newState)
53
- ? Builder(validate, apiDefinition)(newState)
54
- : Builder(validate, apiDefinition)(newState);
55
- };
56
- });
57
-
58
- return { ...fluent, escapeApi } as FluentApi<ToFluent<MutationApi<TApi>, TEscapeApi>, TEscapeApi, TExclude | "test">;
59
- };
60
- }
@@ -1,35 +0,0 @@
1
- // #autoindex
2
-
3
- // #region autoindexed files
4
- // index last changed at: 1st Jan, 2022, 03:27 PM ( GMT-8 )
5
- // hash-code: da242834
6
-
7
-
8
-
9
- // #endregion
10
-
11
- // This file was created by running: "dd autoindex"; it assumes you have
12
- // the 'do-devops' pkg (that's "dd" on npm) installed as a dev dep.
13
- //
14
- // By default it assumes that exports are named exports but this can be changed by
15
- // adding a modifier to the '// #autoindex' syntax:
16
- //
17
- // - autoindex:named same as default, exports "named symbols"
18
- // - autoindex:default assumes each file is exporting a default export and
19
- // converts the default export to the name of the file
20
- // - autoindex:offset assumes files export "named symbols" but that each
21
- // file's symbols should be offset by the file's name
22
- //
23
- // You may also exclude certain files or directories by adding it to the
24
- // autoindex command. As an example:
25
- //
26
- // - autoindex:named, exclude: foo,bar,baz
27
- //
28
- // Inversely, if you state a file to be an "orphan" then autoindex files
29
- // below this file will not reference this autoindex file:
30
- //
31
- // - autoindex:named, orphan
32
- //
33
- // All content outside the "// #region" section in this file will be
34
- // preserved in situations where you need to do something paricularly awesome.
35
- // Keep on being awesome.
@@ -1,310 +0,0 @@
1
- /* eslint-disable unicorn/consistent-function-scoping */
2
- import { Builder, BuilderApi, BuilderComplete } from "../Builder";
3
- import { isNonNullObject } from "common-types";
4
- import { Expect, ExpectExtends, ExpectFalse, Equal } from "@type-challenges/utils";
5
- import { FluentApi, Keys, ToFluent, TypeGuard } from "~/types";
6
- import { createBuilder, BuilderState } from "../../tests/data";
7
-
8
- describe("Builder", () => {
9
- it("BuilderComplete utilty can detect when builder is complete", () => {
10
- type A = { foo: string };
11
- type B = { foo: string; bar: number };
12
- type T = BuilderComplete<A, B>;
13
-
14
- type State = { foo: number; bar: number };
15
- const s = { foo: 43, bar: 12 };
16
- type S = typeof s;
17
- type Done = BuilderComplete<S, State>;
18
-
19
- const n = { foo: 4 };
20
- type N = typeof n;
21
- type NotDone = BuilderComplete<N, State>;
22
-
23
- const p: Partial<State> = { foo: 4 }; // not done
24
- type P = BuilderComplete<typeof p, State>;
25
- const p2: Partial<State> = { foo: 4, bar: 12 }; // done
26
- type P2 = BuilderComplete<typeof p2, State>;
27
- const p3: Partial<State> | State = { foo: 4, bar: 12 }; // done (but masked)
28
- type P3 = BuilderComplete<typeof p3, State>;
29
-
30
- const identity = <T extends Partial<State>>(s: T) => s;
31
- const pf = identity({ foo: 4, bar: 12 }); // done
32
- type PF = BuilderComplete<typeof pf, State>;
33
- const pf2 = identity(p3); // done (but masked)
34
- type PF2 = BuilderComplete<typeof pf2, State>;
35
-
36
- const i2 = <I extends Partial<State>>(i: I, tg: TypeGuard<State>) => {
37
- return tg(i) ? (i as State) : i;
38
- };
39
- const typeGuard: TypeGuard<State> = (i: unknown): i is State => {
40
- return isNonNullObject(i) && (i as any).foo !== undefined && (i as any).bar !== undefined;
41
- };
42
- const pf3 = i2(p2, typeGuard);
43
- type PF3 = BuilderComplete<typeof pf3, State>;
44
-
45
- const tgrDone = typeGuard(p3);
46
- const tgrNotDone = typeGuard(p);
47
- type Complete<T> = T extends true ? "" : "unwrap";
48
- type TgDone = Complete<typeof tgrDone>;
49
- type TgNotDone = Complete<typeof tgrNotDone>;
50
-
51
- type cases = [
52
- // simple type comparison of A to B
53
- Expect<Equal<T, "unwrap">>,
54
- // When using _typeof_ for types the Done/NotDone
55
- // shows clear switching between the two states
56
- Expect<Equal<Done, "">>,
57
- Expect<Equal<NotDone, "unwrap">>,
58
- // when a type is expressed as a Partial<T> we seem
59
- // to lose the ability to identify the completed state
60
- Expect<Equal<P, "unwrap">>,
61
- // this really IS complete (so should eval to true)
62
- ExpectFalse<Equal<P2, "">>,
63
- // unfortunately changing Partial<T> to a union of
64
- // Parial<T> | T does not address this but instead
65
- // just changes the result to a union as well
66
- Expect<Equal<P3, "" | "unwrap">>,
67
- // To get around this limitation, we can try a simple
68
- // identity function. Niavely this works when you call
69
- // it with a runtime object with no explicit typing
70
- Expect<Equal<PF, "">>,
71
- // But sadly, when you pass in a type which has already
72
- // been set as a Partial, it still fails in the same way
73
- Expect<Equal<PF2, "" | "unwrap">>,
74
- // More surprisingly, an identity function which is given
75
- // a type guard to detect whether the type is T or Partial<T>
76
- // still returns a union
77
- Expect<Equal<PF3, "" | "unwrap">>,
78
- // Surely not all is lost though, as the Type Guard is a binary switch
79
- // for the type system, trying to tie directly into this though
80
- // we get the same union results
81
- Expect<Equal<TgDone, "" | "unwrap">>,
82
- Expect<Equal<TgNotDone, "" | "unwrap">>
83
- ];
84
- const cases: cases = [true, true, true, true, false, true, false];
85
- });
86
-
87
- it("Api endpoints can be defined independently but work off same state object", () => {
88
- type State = { foo: number; bar: number; baz?: string };
89
- const endpoint = BuilderApi<State>();
90
- const f1 = endpoint("incFoo", (s) => () => ({
91
- ...s,
92
- foo: s.foo ? s.foo++ : 1,
93
- }));
94
- const f2 = endpoint("decFoo", (s) => () => ({ ...s, foo: s.foo ? s.foo-- : 0 }));
95
- const f3 = endpoint("setFoo", (s) => (foo: number) => ({ ...s, foo }));
96
-
97
- const s: Partial<State> = { foo: 0, bar: 0 };
98
- const inc = f1.incFoo(s);
99
- const dec = f2.decFoo(s);
100
- const set = f3.setFoo(s);
101
-
102
- const incremented = inc();
103
- expect(incremented.foo).toBe(1);
104
- const decremented = dec();
105
- expect(decremented.foo).toBe(0);
106
- expect(set(45).foo).toBe(45);
107
- });
108
-
109
- it("API endpoints can be composed external to builder but accepted as valid param input to Builder", () => {
110
- type State = { foo: number; bar: number; baz?: string };
111
- const endpoint = BuilderApi<State>();
112
- /** increment the value of Foo */
113
- const f1 = endpoint("incFoo", (s) => () => ({
114
- ...s,
115
- foo: s.foo ? s.foo++ : 1,
116
- }));
117
- const f2 = endpoint("decFoo", (s) => () => ({ ...s, foo: s.foo ? s.foo-- : 0 }));
118
-
119
- /**
120
- * composed dictionary of `MutationIdentity` functions representing an
121
- *an API _capability_ for the builder
122
- */
123
- const composed = { ...f1, ...f2 };
124
-
125
- type BuilderApi = Parameters<typeof Builder>[1];
126
- type ComposedApi = typeof composed;
127
-
128
- // design-time type validation
129
- type cases = [Expect<ExpectExtends<BuilderApi, ComposedApi>>];
130
- const cases: cases = [true];
131
- expect(cases).toBe(cases);
132
- });
133
-
134
- it("fluent API fully exposed to type system and ReturnType validates fluent API", () => {
135
- const initialState = { foo: 1, bar: 2 };
136
- const builder = createBuilder(initialState);
137
- type Escape<T extends object = {}> = { unwrap: () => BuilderState; current: Partial<BuilderState> } & T;
138
- type ExplicitType = FluentApi<ToFluent<typeof initialState, Escape>, Escape, "" | "test">;
139
- type B = typeof builder;
140
- type RT = ReturnType<B["incFoo"]>;
141
-
142
- type cases = [
143
- Expect<ExpectExtends<Keys<B>, "incFoo">>,
144
- Expect<ExpectExtends<Keys<B>, "decFoo">>,
145
- Expect<ExpectExtends<Keys<B>, "setFoo">>,
146
- Expect<ExpectExtends<Keys<B>, "incBar">>,
147
- Expect<ExpectExtends<Keys<B>, "setBar">>,
148
- Expect<ExpectExtends<Keys<B>, "setBaz">>,
149
- // and since config is "complete" unwrap is exposed too
150
- Expect<ExpectExtends<Keys<B>, "unwrap">>,
151
- // Endpoints return the same fluent API
152
- Expect<Equal<B, RT>>
153
- ];
154
- const cases: cases = [true, true, true, true, true, true, true, true];
155
- expect(cases).toBe(cases);
156
- });
157
-
158
- it("simple omit test", () => {
159
- const omit = <T extends object, K extends keyof T>(dict: T, key: K) => {
160
- return dict as Omit<T, K>;
161
- };
162
- const obj = { foo: 1, bar: 2 };
163
- const obj2 = omit(obj, "bar");
164
- type O2 = typeof obj2;
165
-
166
-
167
- type Start = { foo: number, bar: number };
168
- const obj3: Start = { foo: 1, bar: 2 };
169
- const obj4 = omit(obj3, "bar");
170
- type O4 = typeof obj4;
171
- });
172
-
173
- it.skip("fluent API can be partially masked with exclusions", () => {
174
- const builder = createBuilder({ foo: 1 });
175
- type B = typeof builder;
176
- type RT = ReturnType<B["incFoo"]>;
177
-
178
- type cases = [
179
- Expect<ExpectExtends<Keys<B>, "incFoo">>,
180
- Expect<ExpectExtends<Keys<B>, "decFoo">>,
181
- Expect<ExpectExtends<Keys<B>, "setFoo">>,
182
- Expect<ExpectExtends<Keys<B>, "incBar">>,
183
- Expect<ExpectExtends<Keys<B>, "setBar">>,
184
- Expect<ExpectExtends<Keys<B>, "setBaz">>,
185
- // and since config is not complete, unwrap is hidden
186
- ExpectFalse<ExpectExtends<Keys<B>, "unwrap">>,
187
- // Endpoints return the same fluent API
188
- Expect<Equal<B, RT>>
189
- ];
190
- const cases: cases = [true, true, true, true, true, true, false, true];
191
- expect(cases).toBe(cases);
192
- });
193
-
194
- it("unwrap() is available when initial state is 'done'", () => {
195
- const builder = createBuilder({ foo: 1, bar: 2 });
196
- type B = typeof builder;
197
-
198
- type cases = [
199
- // unwrap is available on API surface
200
- Expect<ExpectExtends<Keys<B>, "unwrap">>,
201
- // unwrap is a function
202
- Expect<ExpectExtends<Function, B["unwrap"]>>,
203
- // unwrap returns BuilderState
204
- Expect<Equal<ReturnType<B["unwrap"]>, BuilderState>>
205
- ];
206
- const cases: cases = [true, true, true];
207
- expect(cases).toBe(cases);
208
-
209
- const result = builder.unwrap();
210
- expect(result.foo).toBe(1);
211
- expect(result.bar).toBe(2);
212
- });
213
-
214
- it("unwrap() is masked when initial state is not 'done'", () => {
215
- const builder = createBuilder({ foo: 1 });
216
- type B = typeof builder;
217
-
218
- // bar is required but currently undefined
219
- expect(builder.current.bar).toBe(undefined);
220
-
221
- type cases = [
222
- // unwrap is not available on API surface
223
- ExpectFalse<ExpectExtends<Keys<B>, "unwrap">>
224
- ];
225
- const cases: cases = [false];
226
- expect(cases).toBe(cases);
227
-
228
- // even though type system can't see it, run time still has
229
- // the unwrap() function but it should return an error
230
- try {
231
- (builder as any).unwrap();
232
- throw new Error("incomplete builder should throw error when unwrapped");
233
- } catch {
234
- expect(true).toBe(true);
235
- }
236
- });
237
-
238
- it("when initial state is NOT complete, completing state leads to unwrap() being exposed", () => {
239
- const builder = createBuilder({ foo: 1 });
240
- const notDone = builder.decFoo().decFoo().decFoo().decFoo().decFoo().incFoo();
241
- const done = builder.incBar().incBar().incFoo();
242
-
243
- type cases = [
244
- // a builder which is _not_ done should have unwrap() hidden
245
- Expect<ExpectExtends<Keys<typeof notDone>, "unwrap">>,
246
- // in contrast, the builder which is done _should_ have unwrap() available
247
- Expect<ExpectExtends<Keys<typeof done>, "">>
248
- ];
249
- const cases: cases = [false, true];
250
- expect(cases).toBe(cases);
251
- });
252
-
253
- it("when initial state IS complete, unwrap() is available to start but reducing state to an incomplete state removes the unwrap() part of API", () => {
254
- type State = { foo: number; bar: number; baz?: string };
255
- const endpoint = BuilderApi<State>();
256
- /** increment the value of Foo */
257
- const f1 = endpoint("incFoo", (s) => () => ({
258
- ...s,
259
- foo: s.foo ? s.foo++ : 1,
260
- }));
261
- const f2 = endpoint("decFoo", (s) => () => ({ ...s, foo: s.foo ? s.foo-- : 0 }));
262
- const f3 = endpoint("incBar", (s) => () => ({
263
- ...s,
264
- bar: s.bar ? s.bar++ : 1,
265
- }));
266
- const f4 = endpoint("clear", () => () => ({}));
267
- const composed = { ...f1, ...f2, ...f3, ...f4 };
268
- const tg: TypeGuard<State> = (input: unknown): input is State => {
269
- return (
270
- isNonNullObject(input) &&
271
- typeof (input as State).foo === "number" &&
272
- typeof (input as State).bar === "number"
273
- );
274
- };
275
- const builder = Builder(tg, composed)({ foo: 1, bar: 0 });
276
-
277
- const t1 = builder.decFoo().decFoo().decFoo().decFoo().decFoo().incFoo();
278
- const t2 = builder.clear();
279
- console.log({ t1, t2 });
280
- });
281
-
282
- it("Type extends Partial of itself", () => {
283
- type State = { foo: number; bar: number };
284
- type State2 = { foo: number; bar: number };
285
- type PState = Partial<State>;
286
-
287
- const ident = <T extends PState>(s: T): T => s;
288
-
289
- const ex1 = ident({ foo: 1 });
290
- const ex2 = ident({ foo: 1, bar: 1 });
291
-
292
- type cases = [
293
- // value extends partial but no full state
294
- Expect<ExpectExtends<PState, typeof ex1>>,
295
- ExpectFalse<ExpectExtends<State, typeof ex1>>,
296
- // value extends both partial and full state
297
- Expect<ExpectExtends<PState, typeof ex2>>,
298
- Expect<ExpectExtends<State, typeof ex2>>,
299
- // State extends PState
300
- Expect<ExpectExtends<PState, State>>,
301
- ExpectFalse<ExpectExtends<State, PState>>,
302
-
303
- // Two identical states extend one another
304
- Expect<ExpectExtends<State, State2>>,
305
- Expect<ExpectExtends<State2, State>>
306
- ];
307
-
308
- const cases: cases = [true, false, true, true, true, false, true, true];
309
- });
310
- });
@@ -1,59 +0,0 @@
1
-
2
- import { RuleSet } from "~/utility";
3
- type State = { foo: number; bar: number; baz: { a: number; b: number } };
4
- const rules1 = { foo: true, bar: true, yup: () => true, nope: () => false, maybe: (s: State) => s.foo > 5 } as const;
5
- import { Equal, Expect } from "@type-challenges/utils";
6
-
7
-
8
- describe("RuleSet", () => {
9
-
10
- it("stages of application produce correct signatures", () => {
11
- const s1 = RuleSet<State>();
12
- type S1 = typeof s1;
13
- const s2 = s1(rules1);
14
- const sl2 = s1({ foo: true, bar: true, yup: () => true, nope: () => false, maybe: (s: State) => s.foo > 5 });
15
- type S2 = typeof s2;
16
- type SL2 = typeof sl2;
17
- const s3 = s2({ foo: 8, bar: 0, baz: { a: 1, b: 2 } });
18
- type S3 = typeof s3;
19
-
20
-
21
- type cases = [
22
- // the types returned by the 2nd function call should
23
- // always be narrow in type definition, even if not explicitly
24
- // stated as as `as const`.
25
- // critically we we want to distinguish "true" from "false"
26
- Expect<Equal<S2, SL2>>,
27
- // at the end we need to arrive at a type which is a string
28
- // literal representing the key values which have passed the
29
- // rules.
30
- ];
31
-
32
- });
33
-
34
- it("dynamic rules are evaluated correctly", () => {
35
- const noState = RuleSet<State>()(rules1)();
36
- type NoState = typeof noState;
37
- const maybePassing = RuleSet<State>()(rules1)({ foo: 8, bar: 0, baz: { a: 1, b: 2 } });
38
- type Pass = typeof maybePassing;
39
- const maybeFailing = RuleSet<State>()(rules1)({ foo: 0, bar: 0, baz: { a: 1, b: 2 } });
40
- type Fail = typeof maybeFailing;
41
-
42
- type cases = [
43
- // if no state is passed then only static rules are passed forward
44
- Expect<Equal<NoState, "foo" | "bar">>,
45
- // "foo" is greater than 5 so results in "maybe" being truthy
46
- Expect<Equal<Pass, "foo" | "bar" | "yup" | "maybe">>,
47
- // "foo" is less than 5 and therefore "maybe" is falsy
48
- Expect<Equal<Fail, "foo" | "bar" | "yup">>,
49
- ];
50
- const cases: cases = [true, true, true];
51
- expect(cases).toBe(cases);
52
- });
53
-
54
- it("static rulesets operate to exclude parts of an api", () => {
55
- // const rules1 = RuleSet()({ foo: true })();
56
- // const rules2 = RuleSet()({ foo: true, baz: true })();
57
-
58
- });
59
- });
@@ -1,60 +0,0 @@
1
- import type { Expect, Equal, ExpectExtends } from "@type-challenges/utils";
2
- import { Keys, ToFluent } from "~/types";
3
- import { BuilderState } from "./data";
4
-
5
- const api = {
6
- foo: () => 5,
7
- bar: () => "hi",
8
- baz: (greeting: string) => greeting,
9
- };
10
- type Api = typeof api;
11
- type Fluent = ToFluent<Api>;
12
-
13
- describe("ToFluent<T,X> type utility", () => {
14
- it("a simple function-based API has it's return types changed to point back to API", () => {
15
- type cases = [
16
- // all return types on the API return the same Fluent API
17
- Expect<Equal<Fluent, ReturnType<Fluent["foo"]>>>,
18
- Expect<Equal<Fluent, ReturnType<Fluent["bar"]>>>,
19
- Expect<Equal<Fluent, ReturnType<Fluent["baz"]>>>,
20
- // this is also true recursively
21
- Expect<Equal<Fluent, ReturnType<ReturnType<Fluent["foo"]>["bar"]>>>
22
- ];
23
- const cases: cases = [true, true, true, true];
24
- expect(cases).toBe(cases);
25
- });
26
-
27
- // this allows for non fluent parts of the API to be continued on too as part of fluent
28
- // process.
29
- it("an API <T> can be made fluent but while including the type <X> as part of the return type", () => {
30
- const x = {
31
- hi: () => "hello",
32
- };
33
- type X = typeof x;
34
- type FluentX = ToFluent<Api, X>;
35
-
36
- type cases = [
37
- // all return types on the API return the same Fluent API
38
- Expect<Equal<FluentX, ReturnType<FluentX["foo"]>>>,
39
- Expect<Equal<FluentX, ReturnType<FluentX["bar"]>>>,
40
- Expect<Equal<FluentX, ReturnType<FluentX["baz"]>>>,
41
- // this is also true recursively
42
- Expect<Equal<FluentX, ReturnType<ReturnType<FluentX["foo"]>["bar"]>>>,
43
- // the non-fluent API also shows up
44
- Expect<ExpectExtends<Keys<FluentX>, "hi">>,
45
- // the "hi" function returns a string (and breaks out of the fluent loop)
46
- Expect<Equal<string, ReturnType<FluentX["hi"]>>>
47
- ];
48
- const cases: cases = [true, true, true, true, true, true];
49
- expect(cases).toBe(cases);
50
- });
51
-
52
- it("using API structures similar to a builder", () => {
53
- const identityApi = {
54
- incFoo: <T extends Partial<BuilderState>, T2 extends Partial<BuilderState>>(s: T) => () => ({ ...s, foo: s.foo ? s.foo : 1 } as T2),
55
- decBar: <T extends Partial<BuilderState>>(s: T) => () => ({ ...s, foo: s.foo ? s.foo : 1 } as T),
56
- };
57
- const escapeApi = {};
58
- type Escape<T extends object = {}> = { unwrap: () => BuilderState; current: Partial<BuilderState> } & T;
59
- });
60
- });