Repository navigation
Expand file tree
/
Copy pathapp.ts
More file actions
124 lines (111 loc) · 4.01 KB
/
Copy pathapp.ts
File metadata and controls
124 lines (111 loc) · 4.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// Both versions declare the same UserRepository shape, so the repositories
// in this file serve either one.
import {
RegisterUser,
type RegisterUserError,
type User,
type UserRepository,
} from "./result/register-user.js";
import { InvalidEmailError } from "./throwing/email.js";
import {
EmailTakenError,
RegisterUserThrowing,
} from "./throwing/register-user.js";
// A database that is down: it throws, it does not return a Result.
const unreachable: UserRepository = {
existsByEmail: () => Promise.reject(new Error("connection refused")),
save: () => Promise.reject(new Error("connection refused")),
};
console.log("--- with exceptions ---");
const withExceptions = new RegisterUserThrowing(inMemoryRepository());
await registerWithExceptions(withExceptions, "Ada@Example.com");
// -> Registered ada@example.com
await registerWithExceptions(withExceptions, "not-an-email");
// -> Email must look like name@domain.tld
await registerWithExceptions(withExceptions, "ADA@example.com");
// -> That email is already registered
await registerWithExceptions(
new RegisterUserThrowing(unreachable),
"grace@example.com",
);
// -> Something went wrong, try again later
// -> (stderr) Error: connection refused
console.log("--- with Result ---");
const withResult = new RegisterUser(inMemoryRepository());
await registerWithResult(withResult, "Ada@Example.com");
// -> Registered ada@example.com
await registerWithResult(withResult, "not-an-email");
// -> Email must look like name@domain.tld
await registerWithResult(withResult, "ADA@example.com");
// -> That email is already registered
await registerWithResult(new RegisterUser(unreachable), "grace@example.com");
// -> Something went wrong, try again later
// -> (stderr) Error: connection refused
// execute() promises a User and says nothing about failing. Every failure,
// expected or not, arrives in the catch as `unknown`, and telling them apart
// is a chain of instanceof checks kept by hand: drop one and it still
// compiles, and that expected failure gets reported as unexpected.
async function registerWithExceptions(
registerUser: RegisterUserThrowing,
email: string,
): Promise<void> {
try {
const user = await registerUser.execute({ email });
console.log(`Registered ${user.email.value}`);
} catch (error) {
if (error instanceof InvalidEmailError) {
console.log(error.message);
} else if (error instanceof EmailTakenError) {
console.log("That email is already registered");
} else {
reportUnexpected(error);
}
}
}
// execute() names the failures it can return, and the value cannot be
// reached without checking `ok`. The expected failures never reach the
// catch, so all it has left to do is report the unexpected ones.
async function registerWithResult(
registerUser: RegisterUser,
email: string,
): Promise<void> {
try {
const result = await registerUser.execute({ email });
if (!result.ok) {
console.log(toMessage(result.error));
return;
}
console.log(`Registered ${result.value.email.value}`);
} catch (error) {
reportUnexpected(error);
}
}
// A new code in RegisterUserError stops this from compiling until it has a
// message here.
function toMessage(error: RegisterUserError): string {
switch (error.code) {
case "invalidEmail":
return error.detail;
case "emailTaken":
return "That email is already registered";
}
}
// A bug or the infrastructure failing: the person gets a generic message, and
// the details go where an operator will look.
function reportUnexpected(error: unknown): void {
console.log("Something went wrong, try again later");
console.error(String(error));
}
// Every call returns a fresh, empty store, so the two halves of this demo
// do not see each other's users.
function inMemoryRepository(): UserRepository {
const users: User[] = [];
return {
existsByEmail: (email) =>
Promise.resolve(users.some((user) => user.email.value === email.value)),
save: (user) => {
users.push(user);
return Promise.resolve();
},
};
}