tshex-cli 1.0.28 → 1.0.30
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/build/main.js +1 -158
- package/docs/context-ports.md +66 -28
- package/docs/generated-file-reference.md +2 -1
- package/docs/library-structure.md +22 -6
- package/docs/shared/application/data.md +188 -106
- package/docs/shared/application/services.md +90 -87
- package/docs/shared/domain/aggregates.md +22 -131
- package/docs/shared/domain/entities.md +96 -51
- package/package.json +7 -8
- package/source/main.ts +34 -11
- package/templates/ctx/example-ports.ts +1 -0
- package/templates/lib/shared/application/data/capabilities.ts +60 -0
- package/templates/lib/shared/application/data/drivers.ts +2 -2
- package/templates/lib/shared/application/data/managers.ts +5 -62
- package/templates/lib/shared/application/data/repositories.ts +7 -19
- package/templates/lib/shared/application/events.ts +1 -1
- package/templates/lib/shared/application/http/errors.ts +2 -0
- package/templates/lib/shared/application/loggers.ts +0 -21
- package/templates/lib/shared/domain/entities.ts +1 -1
- package/templates/lib/shared/application/http/handlers.ts +0 -13
- package/templates/lib/shared/application/http/json-api.ts +0 -611
- package/templates/lib/shared/application/http/json-web-token.ts +0 -980
- package/templates/lib/shared/application/http/opengraph.ts +0 -533
|
@@ -1,176 +1,259 @@
|
|
|
1
1
|
### Data
|
|
2
2
|
|
|
3
3
|
The data contracts define how the application layer interacts with plain source
|
|
4
|
-
records.
|
|
5
|
-
They separate connection management, raw record access, and domain
|
|
4
|
+
records. They separate connection management, raw record access, and domain
|
|
6
5
|
transformation so that a context can change drivers without rewriting its use
|
|
7
6
|
cases.
|
|
8
7
|
|
|
9
|
-
The generated structure splits this concern into
|
|
8
|
+
The generated structure splits this concern into four files:
|
|
10
9
|
|
|
11
|
-
1. `
|
|
12
|
-
2. `
|
|
13
|
-
3. `
|
|
10
|
+
1. `capabilities.ts` for operation capability interfaces;
|
|
11
|
+
2. `drivers.ts` for connection adapters;
|
|
12
|
+
3. `managers.ts` for plain-record operations;
|
|
13
|
+
4. `repositories.ts` for record-to-domain transformation.
|
|
14
14
|
|
|
15
|
-
####
|
|
15
|
+
#### Capabilities
|
|
16
16
|
|
|
17
|
-
`
|
|
18
|
-
|
|
17
|
+
`capabilities.ts` declares the operation interfaces that a manager implements
|
|
18
|
+
to advertise what it supports.
|
|
19
19
|
|
|
20
|
-
```ts title="shared/application/data/
|
|
21
|
-
|
|
20
|
+
```ts title="shared/application/data/capabilities.ts"
|
|
21
|
+
export interface Listable<DataShape extends Generic = Generic> {
|
|
22
|
+
all(): DataShape[]
|
|
23
|
+
}
|
|
22
24
|
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
+
export interface Creatable<DataShape extends Generic = Generic, Feedback = unknown> {
|
|
26
|
+
create(data: DataShape): Feedback
|
|
27
|
+
}
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
export interface Deletable<Selector = unknown, Feedback = unknown> {
|
|
30
|
+
delete(selector: Selector): Feedback
|
|
27
31
|
}
|
|
32
|
+
|
|
33
|
+
// Also available: Filterable, Sortable, Updatable, Aggregatable, Relatable
|
|
28
34
|
```
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
Implement only the interfaces that the manager actually supports. Adding
|
|
37
|
+
`Creatable` to a class makes the creation capability explicit and discoverable
|
|
38
|
+
without adding it to the base class.
|
|
39
|
+
|
|
40
|
+
```ts title="enrollment/application/managers.ts"
|
|
41
|
+
import { DataManager } from '../shared/application/data/managers.ts'
|
|
42
|
+
import type {
|
|
43
|
+
Listable,
|
|
44
|
+
Creatable,
|
|
45
|
+
Deletable
|
|
46
|
+
} from '../shared/application/data/capabilities.ts'
|
|
47
|
+
import type { Course } from '../domain/courses.ts'
|
|
48
|
+
import type { Student } from '../domain/students.ts'
|
|
49
|
+
import type { Inscription } from '../domain/inscriptions.ts'
|
|
50
|
+
|
|
51
|
+
type Generic = Record<string, unknown>
|
|
52
|
+
|
|
53
|
+
export type CourseData = ReturnType<Course['toJSON']>
|
|
54
|
+
export type StudentData = ReturnType<Student['toJSON']>
|
|
55
|
+
export type InscriptionData = ReturnType<Inscription['toJSON']>
|
|
56
|
+
|
|
57
|
+
export class InMemoryDatabaseManager
|
|
58
|
+
extends DataManager
|
|
59
|
+
implements Listable, Creatable, Deletable
|
|
60
|
+
{
|
|
61
|
+
[property: string]: unknown
|
|
62
|
+
|
|
63
|
+
constructor(private records: Generic[]) {
|
|
64
|
+
super()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public all(): Generic[] {
|
|
68
|
+
return this.records
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public create(data: Generic): boolean {
|
|
72
|
+
this.records.push(data)
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public delete(selector: Partial<Generic>): boolean {
|
|
77
|
+
const before = this.records.length
|
|
78
|
+
this.records = this.records.filter((record) =>
|
|
79
|
+
Object.entries(selector).every(([key, value]) => record[key] !== value)
|
|
80
|
+
)
|
|
81
|
+
return this.records.length < before
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
32
85
|
|
|
33
86
|
#### Data Manager
|
|
34
87
|
|
|
35
|
-
`DataManager` is
|
|
88
|
+
`DataManager` is the base contract for any data source. `DatasetManager`
|
|
89
|
+
extends it with set operations for contexts that need to combine collections.
|
|
36
90
|
|
|
37
91
|
```ts title="shared/application/data/managers.ts"
|
|
38
92
|
export abstract class DataManager<T = Record<string, unknown>> {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
93
|
+
[property: string]: unknown
|
|
94
|
+
}
|
|
42
95
|
|
|
43
|
-
|
|
96
|
+
export abstract class DatasetManager<T = Record<string, unknown>> extends DataManager<T> {
|
|
97
|
+
[property: string]: unknown
|
|
98
|
+
|
|
99
|
+
public abstract union(other: Array<T>): Array<T>
|
|
100
|
+
public abstract intersection(other: Array<T>): Array<T>
|
|
101
|
+
public abstract difference(other: Array<T>): Array<T>
|
|
102
|
+
public abstract symmetricDifference(other: Array<T>): Array<T>
|
|
103
|
+
public abstract complement(other: Array<T>): Array<T>
|
|
44
104
|
}
|
|
45
105
|
```
|
|
46
106
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
`DatasetManager` extension for set operations.
|
|
107
|
+
Extend `DataManager` to implement a concrete data source. The manager exposes
|
|
108
|
+
raw data without domain transformation. The `InMemoryDatabaseManager` above
|
|
109
|
+
extends `DataManager` and implements `Listable`, `Creatable`, and `Deletable`.
|
|
51
110
|
|
|
52
|
-
####
|
|
111
|
+
#### Driver Adapter
|
|
53
112
|
|
|
54
|
-
|
|
113
|
+
`DriverAdapter` is responsible for connecting to a data source and returning an
|
|
114
|
+
enabled `DataManager`.
|
|
55
115
|
|
|
56
|
-
```ts title="
|
|
57
|
-
|
|
58
|
-
|
|
116
|
+
```ts title="shared/application/data/drivers.ts"
|
|
117
|
+
export abstract class DriverAdapter<M extends DataManager = DataManager> {
|
|
118
|
+
[property: string]: unknown
|
|
59
119
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
active: boolean | null
|
|
120
|
+
public abstract connect(...args: unknown[]): M
|
|
121
|
+
|
|
122
|
+
public abstract disconnect(): unknown
|
|
64
123
|
}
|
|
124
|
+
```
|
|
65
125
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
super()
|
|
69
|
-
}
|
|
126
|
+
Extend `DriverAdapter` to wrap a concrete data source. The driver connects to
|
|
127
|
+
the source, returns an enabled manager, and disconnects when the work is done.
|
|
70
128
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
129
|
+
```ts title="enrollment/application/database.ts"
|
|
130
|
+
import { DriverAdapter } from '../shared/application/data/drivers.ts'
|
|
131
|
+
import { InMemoryDatabaseManager } from './managers.ts'
|
|
132
|
+
|
|
133
|
+
type Generic = Record<string, unknown>
|
|
134
|
+
|
|
135
|
+
export type Database = Record<string, Generic[]>
|
|
136
|
+
|
|
137
|
+
export class InMemoryDatabaseDriver extends DriverAdapter<InMemoryDatabaseManager> {
|
|
138
|
+
[property: string]: unknown
|
|
75
139
|
|
|
76
|
-
|
|
77
|
-
|
|
140
|
+
private manager: InMemoryDatabaseManager | null = null
|
|
141
|
+
|
|
142
|
+
constructor(private readonly database: Database) {
|
|
78
143
|
super()
|
|
79
144
|
}
|
|
80
145
|
|
|
81
|
-
public
|
|
82
|
-
|
|
146
|
+
public connect(collectionKey: string): InMemoryDatabaseManager {
|
|
147
|
+
if (this.database[collectionKey] === undefined) {
|
|
148
|
+
this.database[collectionKey] = []
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.manager = new InMemoryDatabaseManager(this.database[collectionKey])
|
|
152
|
+
return this.manager
|
|
83
153
|
}
|
|
84
154
|
|
|
85
|
-
public
|
|
86
|
-
|
|
155
|
+
public disconnect(): void {
|
|
156
|
+
this.manager = null
|
|
87
157
|
}
|
|
88
158
|
}
|
|
89
159
|
```
|
|
90
160
|
|
|
91
|
-
`
|
|
92
|
-
raw records. The application layer can use both without knowing whether
|
|
93
|
-
source is memory, SQL, or an HTTP-backed adapter.
|
|
161
|
+
`InMemoryDatabaseDriver` owns the connection contract. `InMemoryDatabaseManager`
|
|
162
|
+
owns the raw records. The application layer can use both without knowing whether
|
|
163
|
+
the source is memory, SQL, or an HTTP-backed adapter.
|
|
94
164
|
|
|
95
165
|
#### Repository
|
|
96
166
|
|
|
97
167
|
`Repository` is responsible for transforming raw records into domain-oriented
|
|
98
|
-
representations.
|
|
168
|
+
representations. It holds a reference to the manager and requires `transform()`
|
|
169
|
+
to map a raw record into a domain entity.
|
|
99
170
|
|
|
100
171
|
```ts title="shared/application/data/repositories.ts"
|
|
101
|
-
import { type DataManager } from './managers.js'
|
|
102
|
-
import { type DriverAdapter } from './drivers.js'
|
|
103
|
-
|
|
104
172
|
export abstract class Repository<
|
|
105
|
-
|
|
106
|
-
EntityShape
|
|
173
|
+
RawDataShape = Generic,
|
|
174
|
+
EntityShape = Generic,
|
|
175
|
+
M extends DataManager<RawDataShape> = DataManager<RawDataShape>
|
|
107
176
|
> {
|
|
108
|
-
|
|
177
|
+
[property: string]: unknown
|
|
109
178
|
|
|
110
|
-
public
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
179
|
+
public constructor(public readonly manager: M) {}
|
|
180
|
+
|
|
181
|
+
protected abstract transform(data: RawDataShape, ...args: unknown[]): EntityShape
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Data retrieval operations are not defined in the base class — add them
|
|
186
|
+
explicitly in the concrete class using the capability interfaces from
|
|
187
|
+
`capabilities.ts`.
|
|
188
|
+
|
|
189
|
+
```ts title="enrollment/application/repositories.ts"
|
|
190
|
+
import { Repository } from '../shared/application/data/repositories.ts'
|
|
191
|
+
import { InMemoryDatabaseManager } from './managers.ts'
|
|
192
|
+
import type { CourseData, StudentData, InscriptionData } from './managers.ts'
|
|
193
|
+
import { Course } from '../domain/courses.ts'
|
|
194
|
+
import { Student } from '../domain/students.ts'
|
|
195
|
+
import { Inscription } from '../domain/inscriptions.ts'
|
|
196
|
+
|
|
197
|
+
export class CoursesRepository extends Repository<CourseData, Course, InMemoryDatabaseManager> {
|
|
198
|
+
[property: string]: unknown
|
|
199
|
+
|
|
200
|
+
constructor(manager: InMemoryDatabaseManager) {
|
|
201
|
+
super(manager)
|
|
116
202
|
}
|
|
117
203
|
|
|
118
|
-
protected
|
|
119
|
-
return data.
|
|
204
|
+
protected transform(data: CourseData): Course {
|
|
205
|
+
return new Course(data.name, data.description, data.duration_hours)
|
|
120
206
|
}
|
|
121
207
|
|
|
122
|
-
|
|
208
|
+
public create(course: Course): boolean {
|
|
209
|
+
return this.manager.create(course.toJSON())
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
public delete(course: Course): boolean {
|
|
213
|
+
return this.manager.delete(course.toJSON())
|
|
214
|
+
}
|
|
123
215
|
}
|
|
124
|
-
```
|
|
125
216
|
|
|
126
|
-
|
|
127
|
-
|
|
217
|
+
export class StudentsRepository extends Repository<StudentData, Student, InMemoryDatabaseManager> {
|
|
218
|
+
[property: string]: unknown
|
|
128
219
|
|
|
129
|
-
|
|
220
|
+
constructor(manager: InMemoryDatabaseManager) {
|
|
221
|
+
super(manager)
|
|
222
|
+
}
|
|
130
223
|
|
|
131
|
-
|
|
132
|
-
|
|
224
|
+
protected transform(data: StudentData): Student {
|
|
225
|
+
return new Student(data.name, data.email)
|
|
226
|
+
}
|
|
133
227
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
import { DataManager } from '../../shared/application/data/managers.js'
|
|
228
|
+
public create(student: Student): boolean {
|
|
229
|
+
return this.manager.create(student.toJSON())
|
|
230
|
+
}
|
|
138
231
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
active: boolean | null
|
|
232
|
+
public delete(student: Student): boolean {
|
|
233
|
+
return this.manager.delete(student.toJSON())
|
|
234
|
+
}
|
|
143
235
|
}
|
|
144
236
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
email: string
|
|
148
|
-
active: boolean | null
|
|
149
|
-
}
|
|
237
|
+
export class InscriptionsRepository extends Repository<InscriptionData, Inscription, InMemoryDatabaseManager> {
|
|
238
|
+
[property: string]: unknown
|
|
150
239
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
super(driver)
|
|
240
|
+
constructor(manager: InMemoryDatabaseManager) {
|
|
241
|
+
super(manager)
|
|
154
242
|
}
|
|
155
243
|
|
|
156
|
-
protected transform(data:
|
|
157
|
-
return
|
|
158
|
-
id: data.id,
|
|
159
|
-
email: data.email,
|
|
160
|
-
active: data.active,
|
|
161
|
-
}
|
|
244
|
+
protected transform(data: InscriptionData, student: Student, course: Course): Inscription {
|
|
245
|
+
return new Inscription(student, course, data.enrolled_at)
|
|
162
246
|
}
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
247
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
248
|
+
public create(inscription: Inscription): boolean {
|
|
249
|
+
return this.manager.create(inscription.toJSON())
|
|
250
|
+
}
|
|
169
251
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
252
|
+
public delete(inscription: Inscription): boolean {
|
|
253
|
+
return this.manager.delete(inscription.toJSON())
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
174
257
|
|
|
175
258
|
#### Example Flow
|
|
176
259
|
|
|
@@ -179,8 +262,7 @@ The normal flow of the data abstractions is the following:
|
|
|
179
262
|
```mermaid
|
|
180
263
|
flowchart LR
|
|
181
264
|
service[Service] --> repository[Repository]
|
|
182
|
-
repository -->
|
|
183
|
-
driver --> manager["Data manager"]
|
|
265
|
+
repository --> manager["Data manager"]
|
|
184
266
|
manager --> raw["Raw records"]
|
|
185
267
|
repository --> transformed["Transformed records"]
|
|
186
268
|
transformed --> service
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
### Services
|
|
2
2
|
|
|
3
3
|
An application service is responsible for coordinating domain capabilities and
|
|
4
|
-
collaborators to fulfill a system purpose.
|
|
5
|
-
|
|
6
|
-
objects involved in that use case.
|
|
4
|
+
collaborators to fulfill a system purpose. It defines the process of a use
|
|
5
|
+
case, not the business meaning of the domain objects involved in that use case.
|
|
7
6
|
|
|
8
7
|
The generated template provides `Service` as a semantic base class for these
|
|
9
8
|
processes.
|
|
@@ -13,111 +12,115 @@ processes.
|
|
|
13
12
|
`Service` is an abstract class with no concrete behavior.
|
|
14
13
|
|
|
15
14
|
```ts title="shared/application/services.ts"
|
|
16
|
-
export abstract class Service {
|
|
15
|
+
export abstract class Service {
|
|
16
|
+
[property: string]: unknown
|
|
17
|
+
}
|
|
17
18
|
```
|
|
18
19
|
|
|
19
20
|
This design is intentional. The generated class marks the role of the object
|
|
20
|
-
without imposing
|
|
21
|
-
|
|
21
|
+
without imposing a method name, result shape, or framework-specific lifecycle.
|
|
22
|
+
|
|
23
|
+
#### Usage
|
|
24
|
+
|
|
25
|
+
In the following example we build services for a course enrollment context.
|
|
26
|
+
Each service receives its dependencies through the constructor and exposes
|
|
27
|
+
operations that the context ports can call.
|
|
28
|
+
|
|
29
|
+
```ts title="enrollment/application/services.ts"
|
|
30
|
+
import { Service } from '../shared/application/services.ts'
|
|
31
|
+
import { InMemoryDatabaseManager } from './managers.ts'
|
|
32
|
+
import { CoursesRepository, StudentsRepository, InscriptionsRepository } from './repositories.ts'
|
|
33
|
+
import type { Course } from '../domain/courses.ts'
|
|
34
|
+
import type { Student } from '../domain/students.ts'
|
|
35
|
+
import type { Inscription } from '../domain/inscriptions.ts'
|
|
36
|
+
|
|
37
|
+
export class CoursesService extends Service {
|
|
38
|
+
[property: string]: unknown
|
|
39
|
+
|
|
40
|
+
constructor(
|
|
41
|
+
private readonly manager: InMemoryDatabaseManager,
|
|
42
|
+
private readonly repository: CoursesRepository
|
|
43
|
+
) {
|
|
44
|
+
super()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public all() {
|
|
48
|
+
return this.manager.all()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public create(course: Course) {
|
|
52
|
+
return this.repository.create(course)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public delete(course: Course) {
|
|
56
|
+
return this.repository.delete(course)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
22
59
|
|
|
23
|
-
|
|
60
|
+
export class StudentsService extends Service {
|
|
61
|
+
[property: string]: unknown
|
|
24
62
|
|
|
25
|
-
|
|
63
|
+
constructor(
|
|
64
|
+
private readonly manager: InMemoryDatabaseManager,
|
|
65
|
+
private readonly repository: StudentsRepository
|
|
66
|
+
) {
|
|
67
|
+
super()
|
|
68
|
+
}
|
|
26
69
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
70
|
+
public all() {
|
|
71
|
+
return this.manager.all()
|
|
72
|
+
}
|
|
30
73
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
active: boolean | null
|
|
35
|
-
}
|
|
74
|
+
public create(student: Student) {
|
|
75
|
+
return this.repository.create(student)
|
|
76
|
+
}
|
|
36
77
|
|
|
37
|
-
|
|
38
|
-
|
|
78
|
+
public delete(student: Student) {
|
|
79
|
+
return this.repository.delete(student)
|
|
80
|
+
}
|
|
39
81
|
}
|
|
40
82
|
|
|
41
|
-
export class
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
```
|
|
83
|
+
export class InscriptionsService extends Service {
|
|
84
|
+
[property: string]: unknown
|
|
85
|
+
|
|
86
|
+
constructor(
|
|
87
|
+
private readonly manager: InMemoryDatabaseManager,
|
|
88
|
+
private readonly repository: InscriptionsRepository
|
|
89
|
+
) {
|
|
90
|
+
super()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public all() {
|
|
94
|
+
return this.manager.all()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public create(inscription: Inscription) {
|
|
98
|
+
return this.repository.create(inscription)
|
|
99
|
+
}
|
|
62
100
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
The service does not own the email validation rule or the repository transport.
|
|
67
|
-
Those concerns remain in the value object and the adapter-facing collaborator.
|
|
68
|
-
|
|
69
|
-
#### Validation Failures
|
|
70
|
-
|
|
71
|
-
Now consider a process that wants to return an explicit result when the input
|
|
72
|
-
cannot be converted into domain objects.
|
|
73
|
-
|
|
74
|
-
```ts title="users/application/register-user.ts"
|
|
75
|
-
import { Service } from '../../shared/application/services.js'
|
|
76
|
-
import { Email } from '../../shared/domain/value-objects.js'
|
|
77
|
-
import { ValueError } from '../../shared/domain/errors.js'
|
|
78
|
-
|
|
79
|
-
type RegistrationResult =
|
|
80
|
-
| { ok: true; email: string }
|
|
81
|
-
| { ok: false; error: string }
|
|
82
|
-
|
|
83
|
-
export class RegisterUserSafely extends Service {
|
|
84
|
-
public execute(email: string): RegistrationResult {
|
|
85
|
-
try {
|
|
86
|
-
return {
|
|
87
|
-
ok: true,
|
|
88
|
-
email: Email.from(email).value,
|
|
89
|
-
}
|
|
90
|
-
} catch (error: unknown) {
|
|
91
|
-
if (error instanceof ValueError) {
|
|
92
|
-
return {
|
|
93
|
-
ok: false,
|
|
94
|
-
error: error.message,
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
throw error
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
+
public delete(inscription: Inscription) {
|
|
102
|
+
return this.repository.delete(inscription)
|
|
103
|
+
}
|
|
101
104
|
}
|
|
102
105
|
```
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
Each service holds a reference to its manager and repository. The manager
|
|
108
|
+
provides raw list access; the repository delegates creation and deletion
|
|
109
|
+
through the domain entities.
|
|
107
110
|
|
|
108
111
|
> **Warning**
|
|
109
|
-
> `Service` does not guarantee a method name or a result contract. If the
|
|
110
|
-
> needs those conventions, establish them explicitly in project code
|
|
111
|
-
> assuming the generated base class already provides them.
|
|
112
|
+
> `Service` does not guarantee a method name or a result contract. If the
|
|
113
|
+
> codebase needs those conventions, establish them explicitly in project code
|
|
114
|
+
> instead of assuming the generated base class already provides them.
|
|
112
115
|
|
|
113
116
|
#### Example Flow
|
|
114
117
|
|
|
115
118
|
```mermaid
|
|
116
119
|
flowchart LR
|
|
117
|
-
|
|
118
|
-
service -->
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
port[Port] --> service[Service]
|
|
121
|
+
service --> repository[Repository]
|
|
122
|
+
service --> manager["Data manager"]
|
|
123
|
+
repository --> domain["Domain entities"]
|
|
121
124
|
```
|
|
122
125
|
|
|
123
126
|
This flow keeps orchestration in the application layer and domain meaning in
|