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.
- package/.eslintrc +36 -20
- package/.github/workflows/main.yml +34 -0
- package/.github/workflows/other.yml +15 -0
- package/.vscode/settings.json +10 -2
- package/dist/index.d.ts +403 -8538
- package/dist/index.js +35 -60
- package/dist/index.mjs +24 -48
- package/package.json +34 -23
- package/src/shared/valueTypes.ts +1 -1
- package/src/types/ExpandRecursively.ts +10 -1
- package/src/types/FunctionType.ts +1 -1
- package/src/types/SimplifyObject.ts +12 -0
- package/src/types/Transformer.ts +1 -1
- package/src/types/alphabetic/CamelCase.ts +1 -1
- package/src/types/alphabetic/CapFirstAlpha.ts +7 -3
- package/src/types/alphabetic/Dasherize.ts +8 -5
- package/src/types/alphabetic/KebabCase.ts +1 -1
- package/src/types/alphabetic/PascalCase.ts +3 -1
- package/src/types/alphabetic/SnakeCase.ts +8 -3
- package/src/types/dictionary/DictPartialApplication.ts +30 -0
- package/src/types/dictionary/DictPrependWithFn.ts +23 -0
- package/src/types/dictionary/DictReturnValues.ts +27 -0
- package/src/types/dictionary/RequireProps.ts +15 -0
- package/src/types/{type-conversion → dictionary}/SameKeys.ts +0 -0
- package/src/types/dictionary/index.ts +8 -3
- package/src/types/{props.ts → dictionary/props.ts} +1 -1
- package/src/types/functions/FinalReturn.ts +12 -0
- package/{on-hold/types → src/types/functions}/index.ts +4 -3
- package/src/types/index.ts +4 -3
- package/src/types/ruleset-types.ts +3 -3
- package/src/types/runtime.ts +18 -18
- package/src/types/string-literals/LeftWhitespace.ts +5 -2
- package/src/types/string-literals/RightWhitespace.ts +5 -2
- package/src/types/tuples/FirstKey.ts +3 -3
- package/src/types/tuples/FromDictArray.ts +5 -4
- package/src/types/type-conversion/index.ts +2 -5
- package/src/utility/api/api.ts +8 -7
- package/src/utility/createFnWithProps.ts +26 -1
- package/src/utility/dictionary/dictionaryTransform.ts +5 -4
- package/src/utility/dictionary/entries.ts +9 -5
- package/src/utility/dictionary/index.ts +2 -3
- package/src/utility/dictionary/kv/dictToKv.ts +5 -3
- package/src/utility/dictionary/kv/filterDictArray.ts +5 -7
- package/src/utility/dictionary/kv/kv.ts +8 -3
- package/src/utility/dictionary/kv/kvToDict.ts +3 -2
- package/src/utility/dictionary/mapValues.ts +8 -5
- package/src/utility/dictionary/strArrayToDict.ts +1 -2
- package/src/utility/keys.ts +8 -4
- package/src/utility/lists/groupBy.ts +2 -9
- package/src/utility/literals/arrayToObject.ts +29 -29
- package/src/utility/literals/defineType.ts +6 -6
- package/src/utility/literals/identity.ts +5 -2
- package/src/utility/literals/literal.ts +3 -3
- package/src/utility/ruleset.ts +11 -9
- package/src/utility/runtime/condition.ts +1 -1
- package/src/utility/runtime/conditions/isFalse.ts +2 -2
- package/src/utility/runtime/conditions/isFunction.ts +2 -2
- package/src/utility/runtime/conditions/isObject.ts +4 -1
- package/src/utility/runtime/conditions/isTrue.ts +1 -1
- package/src/utility/runtime/ifTypeOf.ts +1 -1
- package/src/utility/runtime/type.ts +3 -1
- package/src/utility/runtime/withValue.ts +5 -3
- package/src/utility/state/Configurator.ts +2 -2
- package/src/utility/state/FluentConfigurator.ts +2 -2
- package/src/utility/state/index.ts +2 -3
- package/tests/CamelCase-spec.ts +2 -0
- package/tests/ExplicitFunction-spec.ts +3 -2
- package/tests/IncludeAndRetain-spec.ts +7 -12
- package/tests/Includes-spec.ts +2 -0
- package/tests/KebabCase-spec.ts +2 -0
- package/tests/MutationIdentity-spec.ts +2 -1
- package/tests/PascalCase-spec.ts +2 -0
- package/tests/Pluralize-spec.ts +2 -0
- package/tests/SnakeCase-spec.ts +2 -0
- package/tests/Where.spec.ts +9 -10
- package/tests/arrayToKeyLookup-spec.ts +5 -5
- package/tests/arrayToObject-spec.ts +12 -8
- package/tests/constructor.spec.ts +2 -0
- package/tests/createFnWithProps.spec.ts +2 -2
- package/tests/defineType-spec.ts +17 -11
- package/tests/dictionary/DictPartialApplication.test.ts +43 -0
- package/tests/dictionary/DictReturnValues.test.ts +32 -0
- package/tests/dictionary/Get.spec.ts +5 -3
- package/tests/dictionary/PrependValuesWithFunction.ts +34 -0
- package/tests/dictionary/RequireProps.test.ts +21 -0
- package/tests/dictionaryTransform-spec.ts +3 -3
- package/tests/ifTypeOf-spec.ts +31 -20
- package/tests/kv/KeyValue-spec.ts +4 -4
- package/tests/kv/SameKeys-spec.ts +6 -9
- package/tests/kv/dict-to-kv-and-back.spec.ts +5 -4
- package/tests/kv/entries-spec.ts +8 -9
- package/tests/kv/keys.spec.ts +9 -19
- package/tests/kv/kv-spec.ts +4 -4
- package/tests/lists/UniqueForProp.spec.ts +3 -1
- package/tests/literal-spec.ts +5 -3
- package/tests/mapValues-spec.ts +5 -3
- package/tests/props-spec.ts +3 -2
- package/tests/runtime/condition.spec.ts +2 -1
- package/tests/runtime/type.spec.ts +4 -3
- package/tests/strArrayToDict-spec.ts +6 -6
- package/tests/string-literals/AllCaps.spec.ts +6 -15
- package/tests/string-literals/CamelCase.spec.ts +13 -32
- package/tests/string-literals/Dasherize.spec.ts +13 -31
- package/tests/string-literals/HasUppercase.spec.ts +6 -15
- package/tests/string-literals/PascalCase.spec.ts +13 -32
- package/tests/string-literals/SnakeCase.spec.ts +13 -32
- package/tests/string-literals/whitespace-capture.spec.ts +10 -28
- package/tests/tuples/TupleToUnion.spec.ts +3 -1
- package/tests/withValue.spec.ts +4 -3
- package/tsconfig.json +8 -19
- package/vitest.config.ts +13 -0
- package/jest.config.ts +0 -20
- package/on-hold/Builder/Builder.ts +0 -66
- package/on-hold/Builder/BuilderApi.ts +0 -21
- package/on-hold/Builder/IdentityToMutationApi.ts +0 -44
- package/on-hold/Builder/MutationToFluentApi.ts +0 -60
- package/on-hold/Builder/index.ts +0 -35
- package/on-hold/tests/Builder-spec.ts +0 -310
- package/on-hold/tests/RuleSet-spec.ts +0 -59
- package/on-hold/tests/ToFluent-spec.ts +0 -60
- package/on-hold/tests/api-spec.ts +0 -73
- package/on-hold/tests/data/builder-data.ts +0 -53
- package/on-hold/tests/filterDictArray-spec.ts +0 -50
- package/on-hold/tests/run-time-spec.ts +0 -44
- package/on-hold/type.ts +0 -20
- package/on-hold/types/object.ts +0 -19
- package/on-hold/types/prop.ts +0 -28
- package/on-hold/types/scalar.ts +0 -37
- package/src/types/dictionary/NotEmptyObject.ts +0 -9
- package/src/types/type-conversion/UnwrapValue.ts +0 -13
- package/src/types/type-conversion/WrapValue.ts +0 -15
- package/src/utility/dictionary/valuesOfProp.ts +0 -13
- 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
|
-
}
|
package/on-hold/Builder/index.ts
DELETED
|
@@ -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
|
-
});
|