Skip to content

Commit

Permalink
feat(#101): finish DeepRequired<T>
Browse files Browse the repository at this point in the history
  • Loading branch information
AshGw committed Apr 26, 2024
1 parent 610f0df commit 7a9d28a
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 8 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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 = '[email protected]'; // TypeError: Cannot add property email, object is not extensible

// Existing properties cannot be re-configured nor deleted

(john as any).email = '[email protected]'; // 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.
Expand All @@ -107,7 +108,7 @@ Decorators like ``@Final`` provide a limited way to emulate final behavior, thes
```typescript
type ResultType = TestType<Type1, Type2, true>;
```
``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)
Expand All @@ -122,7 +123,9 @@ test('|-54| should be 54',() => {
});
````

#### Runtime safety with branded types
#### Branded types
Here, it's called `NewType<N,T>`, 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) {
Expand Down Expand Up @@ -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

Expand Down
49 changes: 48 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1572,6 +1572,54 @@ export type RequiredKeys<T> = {
[K in Keys<T>]-?: EmptyObject extends Pick<T, K> ? 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<Primitive>;
n: Falsy;
};
e: 'foo';
};
};
};
};
type Actual = {
a?: () => 1;
x?: string;
s?: {
q?: Nullable;
s?: {
i?: {
x?: {
o?: Maybe<Primitive>;
n?: Falsy;
};
e?: 'foo';
};
};
};
};
type T = DeepRequired<Actual>; // Result: Expected
* ```
*/
export type DeepRequired<T> = T extends UnknownFunction
? T
: {
[K in Keys<T>]-?: IfExtends<T[K], unknown, DeepRequired<T[K]>, T[K]>;
};

export type IsDeepRequired<T> = IfExtends<T, DeepRequired<T>, true, false>;

/**
* @hidden
*/
Expand Down Expand Up @@ -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<T extends Newable>(cst: T): T & Newable {
return class Locked extends cst {
Expand Down
54 changes: 54 additions & 0 deletions tests/deep-required.test.ts
Original file line number Diff line number Diff line change
@@ -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<Primitive>;
n: Falsy;
};
e: 'foo';
};
};
};
};

type Actual = {
a?: () => 1;
x?: string;
s?: {
q?: Nullable;
s?: {
i?: {
x?: {
o?: Maybe<Primitive>;
n?: Falsy;
};
e?: 'foo';
};
};
};
};

test('_', () => {
const result: TestType<DeepRequired<Actual>, Expected, true> = true;
expect(result).toBe(true);
});

test('_', () => {
const result: TestType<IsDeepMutable<Expected>, true, true> = true;
expect(result).toBe(true);
});

0 comments on commit 7a9d28a

Please sign in to comment.