inferred-types 0.20.0 → 0.22.5

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 (161) 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 +426 -8547
  6. package/dist/index.js +57 -76
  7. package/dist/index.mjs +47 -63
  8. package/package.json +38 -26
  9. package/src/Mutation/index.ts +6 -29
  10. package/src/index.ts +6 -29
  11. package/src/shared/index.ts +6 -29
  12. package/src/shared/valueTypes.ts +1 -1
  13. package/src/types/ExpandRecursively.ts +10 -1
  14. package/src/types/Keys.ts +10 -6
  15. package/src/types/SimplifyObject.ts +12 -0
  16. package/src/types/Transformer.ts +1 -1
  17. package/src/types/alphabetic/CamelCase.ts +2 -2
  18. package/src/types/alphabetic/CapFirstAlpha.ts +7 -3
  19. package/src/types/alphabetic/Dasherize.ts +8 -5
  20. package/src/types/alphabetic/KebabCase.ts +1 -1
  21. package/src/types/alphabetic/PascalCase.ts +3 -1
  22. package/src/types/alphabetic/SnakeCase.ts +8 -3
  23. package/src/types/alphabetic/alpha-characters.ts +33 -9
  24. package/src/types/alphabetic/index.ts +6 -29
  25. package/src/types/dictionary/DictChangeValue.ts +26 -0
  26. package/src/types/dictionary/DictPartialApplication.ts +30 -0
  27. package/src/types/dictionary/DictPrependWithFn.ts +23 -0
  28. package/src/types/dictionary/DictReturnValues.ts +27 -0
  29. package/src/types/dictionary/MutableProps.ts +19 -0
  30. package/src/types/dictionary/RequireProps.ts +15 -0
  31. package/src/types/{type-conversion → dictionary}/SameKeys.ts +0 -0
  32. package/src/types/dictionary/index.ts +13 -30
  33. package/src/types/{props.ts → dictionary/props.ts} +2 -2
  34. package/src/types/fluent/index.ts +6 -29
  35. package/src/types/functions/FinalReturn.ts +12 -0
  36. package/src/types/functions/index.ts +12 -0
  37. package/src/types/index.ts +8 -30
  38. package/src/types/kv/index.ts +6 -29
  39. package/src/types/lists/index.ts +6 -29
  40. package/src/types/ruleset-types.ts +3 -3
  41. package/src/types/runtime.ts +18 -18
  42. package/src/types/string-literals/LeftWhitespace.ts +5 -2
  43. package/src/types/string-literals/RightWhitespace.ts +5 -2
  44. package/src/types/string-literals/index.ts +6 -29
  45. package/src/types/tuples/FirstKey.ts +3 -3
  46. package/src/types/tuples/FromDictArray.ts +5 -4
  47. package/src/types/tuples/index.ts +6 -29
  48. package/src/types/type-conversion/index.ts +6 -32
  49. package/src/utility/api/api.ts +8 -7
  50. package/src/utility/api/index.ts +6 -29
  51. package/src/utility/dictionary/defineProperties.ts +35 -0
  52. package/src/utility/dictionary/dictionaryTransform.ts +5 -4
  53. package/src/utility/dictionary/entries.ts +9 -5
  54. package/src/utility/dictionary/index.ts +7 -30
  55. package/src/utility/dictionary/kv/dictToKv.ts +5 -3
  56. package/src/utility/dictionary/kv/filterDictArray.ts +5 -7
  57. package/src/utility/dictionary/kv/index.ts +6 -29
  58. package/src/utility/dictionary/kv/kv.ts +8 -3
  59. package/src/utility/dictionary/kv/kvToDict.ts +3 -2
  60. package/src/utility/dictionary/mapValues.ts +8 -5
  61. package/src/utility/dictionary/strArrayToDict.ts +1 -2
  62. package/src/utility/errors/ReadOnlyViolation.ts +3 -0
  63. package/src/utility/errors/index.ts +12 -0
  64. package/src/utility/index.ts +7 -29
  65. package/src/utility/keys.ts +8 -4
  66. package/src/utility/lists/groupBy.ts +2 -9
  67. package/src/utility/lists/index.ts +6 -29
  68. package/src/utility/literals/arrayToObject.ts +29 -29
  69. package/src/utility/literals/defineType.ts +6 -6
  70. package/src/utility/literals/identity.ts +5 -2
  71. package/src/utility/literals/index.ts +6 -29
  72. package/src/utility/literals/literal.ts +3 -3
  73. package/src/utility/map-reduce/index.ts +6 -29
  74. package/src/utility/modelling/index.ts +6 -29
  75. package/src/utility/ruleset.ts +11 -9
  76. package/src/utility/runtime/condition.ts +1 -1
  77. package/src/utility/runtime/conditions/index.ts +6 -30
  78. package/src/utility/runtime/conditions/isFalse.ts +2 -2
  79. package/src/utility/runtime/conditions/isFunction.ts +2 -2
  80. package/src/utility/runtime/conditions/isObject.ts +4 -1
  81. package/src/utility/runtime/conditions/isTrue.ts +1 -1
  82. package/src/utility/runtime/ifTypeOf.ts +5 -4
  83. package/src/utility/runtime/index.ts +6 -29
  84. package/src/utility/runtime/type.ts +3 -1
  85. package/src/utility/runtime/withValue.ts +5 -3
  86. package/src/utility/state/Configurator.ts +2 -2
  87. package/src/utility/state/FluentConfigurator.ts +2 -2
  88. package/src/utility/state/index.ts +6 -30
  89. package/tests/CamelCase-spec.ts +2 -0
  90. package/tests/ExplicitFunction-spec.ts +3 -2
  91. package/tests/IncludeAndRetain-spec.ts +7 -12
  92. package/tests/Includes-spec.ts +2 -0
  93. package/tests/KebabCase-spec.ts +2 -0
  94. package/tests/MutationIdentity-spec.ts +2 -1
  95. package/tests/PascalCase-spec.ts +2 -0
  96. package/tests/Pluralize-spec.ts +2 -0
  97. package/tests/SnakeCase-spec.ts +2 -0
  98. package/tests/Where.spec.ts +9 -10
  99. package/tests/arrayToKeyLookup-spec.ts +5 -5
  100. package/tests/arrayToObject-spec.ts +12 -8
  101. package/tests/constructor.spec.ts +2 -0
  102. package/tests/createFnWithProps.spec.ts +2 -2
  103. package/tests/data/index.ts +6 -29
  104. package/tests/defineType-spec.ts +17 -11
  105. package/tests/dictionary/DictChangeValue.test.ts +30 -0
  106. package/tests/dictionary/DictPartialApplication.test.ts +43 -0
  107. package/tests/dictionary/DictReturnValues.test.ts +36 -0
  108. package/tests/dictionary/Get.spec.ts +5 -3
  109. package/tests/dictionary/MutableProps.test.ts +30 -0
  110. package/tests/dictionary/PrependValuesWithFunction.ts +34 -0
  111. package/tests/dictionary/RequireProps.test.ts +21 -0
  112. package/tests/dictionaryTransform-spec.ts +3 -3
  113. package/tests/ifTypeOf-spec.ts +31 -20
  114. package/tests/kv/KeyValue-spec.ts +4 -4
  115. package/tests/kv/SameKeys-spec.ts +6 -9
  116. package/tests/kv/dict-to-kv-and-back.spec.ts +5 -4
  117. package/tests/kv/entries-spec.ts +8 -9
  118. package/tests/kv/keys.spec.ts +9 -19
  119. package/tests/kv/kv-spec.ts +4 -4
  120. package/tests/lists/UniqueForProp.spec.ts +3 -1
  121. package/tests/literal-spec.ts +5 -3
  122. package/tests/mapValues-spec.ts +5 -3
  123. package/tests/props-spec.ts +3 -2
  124. package/tests/runtime/condition.spec.ts +2 -1
  125. package/tests/runtime/type.spec.ts +4 -3
  126. package/tests/strArrayToDict-spec.ts +6 -6
  127. package/tests/string-literals/AllCaps.spec.ts +6 -15
  128. package/tests/string-literals/CamelCase.spec.ts +13 -32
  129. package/tests/string-literals/Dasherize.spec.ts +13 -31
  130. package/tests/string-literals/HasUppercase.spec.ts +6 -15
  131. package/tests/string-literals/PascalCase.spec.ts +13 -32
  132. package/tests/string-literals/SnakeCase.spec.ts +13 -32
  133. package/tests/string-literals/whitespace-capture.spec.ts +10 -28
  134. package/tests/tuples/TupleToUnion.spec.ts +3 -1
  135. package/tests/withValue.spec.ts +4 -3
  136. package/tsconfig.json +8 -19
  137. package/vitest.config.ts +13 -0
  138. package/jest.config.ts +0 -20
  139. package/on-hold/Builder/Builder.ts +0 -66
  140. package/on-hold/Builder/BuilderApi.ts +0 -21
  141. package/on-hold/Builder/IdentityToMutationApi.ts +0 -44
  142. package/on-hold/Builder/MutationToFluentApi.ts +0 -60
  143. package/on-hold/Builder/index.ts +0 -35
  144. package/on-hold/tests/Builder-spec.ts +0 -310
  145. package/on-hold/tests/RuleSet-spec.ts +0 -59
  146. package/on-hold/tests/ToFluent-spec.ts +0 -60
  147. package/on-hold/tests/api-spec.ts +0 -73
  148. package/on-hold/tests/data/builder-data.ts +0 -53
  149. package/on-hold/tests/filterDictArray-spec.ts +0 -50
  150. package/on-hold/tests/run-time-spec.ts +0 -44
  151. package/on-hold/type.ts +0 -20
  152. package/on-hold/types/index.ts +0 -34
  153. package/on-hold/types/object.ts +0 -19
  154. package/on-hold/types/prop.ts +0 -28
  155. package/on-hold/types/scalar.ts +0 -37
  156. package/src/types/dictionary/NotEmptyObject.ts +0 -9
  157. package/src/types/type-conversion/UnwrapValue.ts +0 -13
  158. package/src/types/type-conversion/WrapValue.ts +0 -15
  159. package/src/utility/dictionary/valuesOfProp.ts +0 -13
  160. package/src/utility/runtime/conditions/isLiteral.ts +0 -7
  161. package/src/utility/state/KeyStorage.ts +0 -23
@@ -1,7 +1,8 @@
1
- import { createFnWithProps, type, withValue } from "~/utility";
1
+ import { describe, it, expect } from "vitest";
2
2
  import type { Expect, Equal } from "@type-challenges/utils";
3
- import { WithValue } from "~/types/props";
4
- import { FunctionType } from "~/types";
3
+
4
+ import { createFnWithProps, type, withValue } from "../src/utility";
5
+ import { FunctionType, WithValue } from "../src";
5
6
 
6
7
  describe("withValue()() utility", () => {
7
8
  it("type reduction with WithValue<T> works as expected", () => {
package/tsconfig.json CHANGED
@@ -3,37 +3,26 @@
3
3
  "module": "ESNEXT",
4
4
  "target": "ESNEXT",
5
5
  "moduleResolution": "node",
6
-
7
6
  "sourceMap": true,
8
7
  "noEmit": true,
9
8
  "incremental": true,
10
9
  "resolveJsonModule": true,
11
-
12
10
  "strict": true,
13
11
  "skipLibCheck": true,
14
12
  "noUnusedLocals": true,
15
13
  "noUnusedParameters": true,
16
14
  "noImplicitReturns": true,
17
15
  "allowSyntheticDefaultImports": true,
18
-
19
- "lib": ["ES2020"],
20
-
16
+ "lib": [
17
+ "ES2020"
18
+ ],
21
19
  "baseUrl": ".",
22
20
  "declaration": true,
23
21
  "outDir": "dist",
24
22
  "tsBuildInfoFile": ".tsbuildinfo",
25
-
26
- "paths": {
27
- "~/*": ["src/*"],
28
- "/~/*": ["src/*"]
29
- },
30
- "plugins": [
31
- // Transform paths in output .js files
32
- { "transform": "typescript-transform-paths" },
33
-
34
- // Transform paths in output .d.ts files (Include this line if you output declarations files)
35
- { "transform": "typescript-transform-paths", "afterDeclarations": true }
36
- ]
37
23
  },
38
- "include": ["src", "tests"]
39
- }
24
+ "include": [
25
+ "src",
26
+ "tests"
27
+ ]
28
+ }
@@ -0,0 +1,13 @@
1
+ /// <reference types="vitest" />
2
+ import { defineConfig } from "vite";
3
+
4
+ // used for testing, library code uses TSUP to build exports
5
+ export default defineConfig({
6
+ test: {
7
+ dir: "tests",
8
+ api: {
9
+ host: "0.0.0.0",
10
+ },
11
+ },
12
+ plugins: [],
13
+ });
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
- });