Skip to content

Commit 7972b17

Browse files
authored
feat: examples of TypeScript Avanzado: Mejora tu Developer eXperience
feat: add TypeScript DX course examples
2 parents b2364de + e6bdbb5 commit 7972b17

File tree

23 files changed

+3778
-8749
lines changed

23 files changed

+3778
-8749
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
${{ runner.os }}-
2525
2626
- name: 📥 Install dependencies
27-
run: npm install
27+
run: npm install --force
2828

2929
- name: 💅 Lint code style
3030
run: npm run lint

package-lock.json

Lines changed: 3246 additions & 8743 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,21 @@
44
"version": "1.0.0",
55
"author": "codelytv",
66
"description": "✨ Awesome TypeScript Examples with complete show cases of how to take advantage of the language potential.",
7-
"keywords": ["typescript", "eslint", "examples", "dependency-injection", "decorators", "generics", "enums", "any", "unknown", "exhaustiveness-checking", "utility-types", "typescript-example", "typescript-examples"],
7+
"keywords": [
8+
"typescript",
9+
"eslint",
10+
"examples",
11+
"dependency-injection",
12+
"decorators",
13+
"generics",
14+
"enums",
15+
"any",
16+
"unknown",
17+
"exhaustiveness-checking",
18+
"utility-types",
19+
"typescript-example",
20+
"typescript-examples"
21+
],
822
"homepage": "https://github.com/CodelyTV/awesome-typescript-examples#readme",
923
"repository": {
1024
"type": "git",
@@ -19,12 +33,13 @@
1933
"test": "jest"
2034
},
2135
"devDependencies": {
36+
"@faker-js/faker": "^8.0.2",
2237
"@swc/core": "^1.2.172",
2338
"@swc/jest": "^0.2.20",
2439
"@types/express": "^4.17.13",
2540
"@types/jest": "^27.4.1",
26-
"@typescript-eslint/eslint-plugin": "^5.21.0",
27-
"@typescript-eslint/parser": "^5.21.0",
41+
"@typescript-eslint/eslint-plugin": "^6.1.0",
42+
"@typescript-eslint/parser": "^6.1.0",
2843
"eslint": "^8.14.0",
2944
"eslint-config-prettier": "^8.5.0",
3045
"eslint-plugin-import": "^2.26.0",
@@ -33,7 +48,7 @@
3348
"eslint-plugin-simple-import-sort": "^7.0.0",
3449
"jest": "^28.0.1",
3550
"prettier": "^2.6.2",
36-
"typescript": "^4.6.3"
51+
"typescript": "^5.1.6"
3752
},
3853
"dependencies": {
3954
"diod": "^1.0.2",
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
// Example with "as const"
3+
export type User = { name: string; age: number };
4+
5+
function getUserName<T extends User>(user: T): T["name"] {
6+
return user.name;
7+
}
8+
9+
const userName = getUserName({ name: "Isma", age: 31 } as const);
10+
// ^?
11+
12+
// Ejemplo const type parameter
13+
function getUserAge<const T extends User>(user: T): T["age"] {
14+
return user.age;
15+
}
16+
17+
const userAge = getUserAge({ name: "Isma", age: 31 });
18+
// ^?
19+
20+
// Ejemplo con array y readonly
21+
// En este ejemplo es importante combinar el const con el readonly porque si no el array sería
22+
// mutable dentro de la función y no se infiere el tipo
23+
type HasNames = { names: readonly string[] };
24+
function getNamesExactly<const T extends HasNames>(arg: T): T["names"] {
25+
return arg.names;
26+
}
27+
28+
const names = getNamesExactly({ names: ["Javi", "Ronny", "Isma"] });
29+
// ^?
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { EndpointPayload } from "./EndpointPayload";
2+
3+
export class CreateCoursePayload extends EndpointPayload {
4+
constructor(public readonly title: string, public readonly startDate: Date) {
5+
super();
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { EndpointPayload } from "./EndpointPayload";
2+
3+
export class CreateUserPayload extends EndpointPayload {
4+
constructor(public readonly name: string) {
5+
super();
6+
}
7+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export class EndpointPayload {}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
3+
import { CreateCoursePayload } from "./CreateCoursePayload";
4+
import { CreateUserPayload } from "./CreateUserPayload";
5+
import { EndpointPayload } from "./EndpointPayload";
6+
7+
type Endpoint = {
8+
name: string;
9+
url: string;
10+
payload: new (...params: any) => EndpointPayload;
11+
};
12+
13+
function setupFetcher<
14+
const GetEndpoint extends Endpoint,
15+
const PostEndpoint extends Endpoint
16+
>(endpoints: { get: GetEndpoint[]; post: PostEndpoint[] }) {
17+
const endpointNameToUrl = (method: keyof typeof endpoints, name: string) => {
18+
const t = endpoints[method];
19+
20+
const endpoint = t.find((t) => t.name === name);
21+
22+
if (!endpoint) {
23+
throw new Error(`Unexpected fetcher error: Endpoint ${name} not found`);
24+
}
25+
26+
return endpoint.url;
27+
};
28+
29+
const post = <
30+
TName extends PostEndpoint["name"],
31+
TPayload extends Extract<PostEndpoint, { name: TName }>["payload"]
32+
>(
33+
name: TName,
34+
payload: InstanceType<TPayload>
35+
) => {
36+
console.log(endpointNameToUrl("post", name), payload.toString());
37+
};
38+
39+
return { post };
40+
}
41+
42+
const api = setupFetcher({
43+
get: [],
44+
post: [
45+
{
46+
name: "createUser",
47+
url: "/api/user",
48+
payload: CreateUserPayload,
49+
},
50+
{ name: "createCourse", url: "/api/course", payload: CreateCoursePayload },
51+
],
52+
});
53+
54+
api.post(
55+
"createCourse",
56+
new CreateCoursePayload("TypeScript Avanzado", new Date())
57+
);
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
type ExtractRouteParams<T> = T extends `:${infer P}/${infer Rest}`
3+
? P | ExtractRouteParams<`${Rest}`>
4+
: T extends `:${infer P}`
5+
? P
6+
: T extends `${infer _Start}/${infer Rest}`
7+
? ExtractRouteParams<Rest>
8+
: never;
9+
10+
type RouteParamsObject<T extends string> = {
11+
[key in T]: string;
12+
};
13+
14+
export type Route = {
15+
name: string;
16+
path: string;
17+
};
18+
19+
function buildRouter<const T extends Route>(routes: T[]) {
20+
const getRoute = <
21+
RouteName extends T["name"],
22+
RouteParams extends RouteParamsObject<
23+
ExtractRouteParams<Extract<T, { name: RouteName }>["path"]>
24+
>
25+
>(
26+
name: RouteName,
27+
params: RouteParams
28+
): string => {
29+
const route = routes.find((n) => n.name && n.name === name)!.path;
30+
31+
Object.entries<string>(params).forEach(([key, value]) => {
32+
route.replace(`:${key}`, value);
33+
});
34+
35+
return route;
36+
};
37+
return { getRoute };
38+
}
39+
40+
// Ejemplo de narrowing que explicando el porque funciona la firma anterior
41+
type GeneratedRoutesType =
42+
| { name: "login"; path: "/login"; params: [] }
43+
| { name: "courses"; path: "/courses/:category/:year"; params: ["category"] };
44+
45+
type RouteNames = GeneratedRoutesType["name"];
46+
// ^?
47+
48+
type RouteParams = Extract<GeneratedRoutesType, { name: "courses" }>["params"];
49+
// ^?
50+
51+
type RoutePath = Extract<GeneratedRoutesType, { name: "courses" }>["path"];
52+
// ^?
53+
54+
const { getRoute } = buildRouter([
55+
{
56+
name: "login",
57+
path: "/login",
58+
},
59+
{
60+
name: "courses",
61+
path: "/courses/:category",
62+
},
63+
]);
64+
65+
const route = getRoute("courses", { category: "TypeScript" });
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
export type Route = {
3+
name: string;
4+
path: string;
5+
params: readonly string[];
6+
};
7+
8+
function buildRouter<const T extends Route>(routes: T[]) {
9+
const getRoute = <
10+
RouteName extends T["name"],
11+
RouteParams extends Extract<T, { name: RouteName }>["params"]
12+
>(
13+
name: RouteName,
14+
params: { [key in RouteParams[number]]: string }
15+
): string => {
16+
const route = routes.find((n) => n.name && n.name === name)!.path;
17+
18+
Object.entries<string>(params).forEach(([key, value]) => {
19+
route.replace(`:${key}`, value);
20+
});
21+
22+
return route;
23+
};
24+
return { getRoute };
25+
}
26+
27+
// Ejemplo de narrowing que explicando el porque funciona la firma anterior
28+
type GeneratedRoutesType =
29+
| { name: "login"; path: "/login"; params: [] }
30+
| { name: "courses"; path: "/courses"; params: ["category"] };
31+
32+
type RouteNames = GeneratedRoutesType["name"];
33+
// ^?
34+
35+
type RouteParams = Extract<GeneratedRoutesType, { name: "courses" }>["params"];
36+
// ^?
37+
38+
const { getRoute } = buildRouter([
39+
{
40+
name: "login",
41+
path: "/login",
42+
params: [],
43+
},
44+
{
45+
name: "courses",
46+
path: "/courses/:category",
47+
params: ["category"],
48+
},
49+
]);
50+
51+
const route = getRoute("courses", { category: "typescript" });

0 commit comments

Comments
 (0)