diff --git a/README.md b/README.md index 8bffcdbd..66bc1ce2 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ npm i ts-roids pnpm i ts-roids ``` If you're only using types, you can install it as a ``devDependency``. -And if you're using the decorators, set this property inside `tsconfig.json`. +And if you're using decorators, set this property inside `tsconfig.json`. ```json { "compilerOptions": { @@ -90,10 +90,11 @@ const john = new Person('John', 30); // Existing properties can still be modified john.age = 31; // No Errors -// Trying to add a new property will throw an error -(john as any).email = 'john@example.com'; // TypeError: Cannot add property email, object is not extensible - // Existing properties cannot be re-configured nor deleted + +(john as any).email = 'john@example.com'; // TypeError: Cannot add property email, +// object is not extensible + delete john.age; // TypeError: Cannot delete property 'age' ``` There are many other decorators to choose from, check the [docs](#documentation) for more info. @@ -107,7 +108,7 @@ Decorators like ``@Final`` provide a limited way to emulate final behavior, thes ```typescript type ResultType = TestType; ``` -``TestType`` accepts three arguments: the types you're comparing (``Type1`` and ``Type2``) and a boolean (``true`` if you expected them to match, ``false`` otherwise). The resulting type will tell if your expectation is correct, true if it is, else false. +``TestType`` accepts three arguments: the types you're comparing (``Type1`` and ``Type2``) and a boolean (``true`` if you expected them to match, ``false`` otherwise). The resulting type will tell if your expectation is correct, ``true`` if it is, else ``false``. You can use it however you want, maybe to test a type on the go, or, test using a testing framework. Here's an example with [`vitest`](https://vitest.dev) @@ -122,7 +123,9 @@ test('|-54| should be 54',() => { }); ```` -#### Runtime safety with branded types +#### Branded types +Here, it's called `NewType`, why `NewType` and not `Brand` or `Branded`? Well Python [has](https://docs.python.org/3/library/typing.html#newtype) it already so let's give it the same name. + Can you figure out how many things that can go wrong here? ```typescript function requestBaz(barID: string, fooID: string) { @@ -198,7 +201,6 @@ const baz2 = requestBaz(foo.id, bar.id); Type 'FooID' is not assignable to type '"BarID"' */ ``` -Why `NewType` and not `Brand` or `Branded`? Well Python [has](https://docs.python.org/3/library/typing.html#newtype) it already so let's give it the same name. ## Changelog diff --git a/src/index.ts b/src/index.ts index 8e3c46d5..30721e69 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1572,6 +1572,54 @@ export type RequiredKeys = { [K in Keys]-?: EmptyObject extends Pick ? never : K; }[keyof T]; +/** + * Make all object properties required + * @example + * ```ts +type Expected = { + a: () => 1; + x: string; + s: { + q: Nullable; + s: { + i: { + x: { + o: Maybe; + n: Falsy; + }; + e: 'foo'; + }; + }; + }; +}; + +type Actual = { + a?: () => 1; + x?: string; + s?: { + q?: Nullable; + s?: { + i?: { + x?: { + o?: Maybe; + n?: Falsy; + }; + e?: 'foo'; + }; + }; + }; +}; +type T = DeepRequired; // Result: Expected + * ``` + */ +export type DeepRequired = T extends UnknownFunction + ? T + : { + [K in Keys]-?: IfExtends, T[K]>; + }; + +export type IsDeepRequired = IfExtends, true, false>; + /** * @hidden */ @@ -1687,7 +1735,6 @@ foo.bar = 'altered bar'; // The line below will cause a TypeError: Cannot delete property 'bar' delete foo.bar; * ``` - * */ export function Frozen(cst: T): T & Newable { return class Locked extends cst { diff --git a/tests/deep-required.test.ts b/tests/deep-required.test.ts new file mode 100644 index 00000000..f495c0f8 --- /dev/null +++ b/tests/deep-required.test.ts @@ -0,0 +1,54 @@ +import { + Falsy, + DeepRequired, + Primitive, + IsDeepMutable, + Maybe, + Nullable, + TestType, +} from 'src'; +import { test, expect } from 'vitest'; + +type Expected = { + a: () => 1; + x: string; + s: { + q: Nullable; + s: { + i: { + x: { + o: Maybe; + n: Falsy; + }; + e: 'foo'; + }; + }; + }; +}; + +type Actual = { + a?: () => 1; + x?: string; + s?: { + q?: Nullable; + s?: { + i?: { + x?: { + o?: Maybe; + n?: Falsy; + }; + e?: 'foo'; + }; + }; + }; +}; + +test('_', () => { + const result: TestType, Expected, true> = true; + expect(result).toBe(true); +}); + +test('_', () => { + const result: TestType, true, true> = true; + expect(result).toBe(true); +});