tshex-cli 1.0.29 → 1.0.31

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.
@@ -21,7 +21,7 @@ surface that another actor can call or observe.
21
21
 
22
22
  The generated context starts with a single root file for ports.
23
23
 
24
- ```ts title="users/example-ports.ts"
24
+ ```ts title="enrollment/example-ports.ts"
25
25
  export class ExamplePort {
26
26
  public doSomething(): void {
27
27
  // ...
@@ -39,32 +39,56 @@ expected to be a module that defines one or more context ports.
39
39
  #### First Port
40
40
 
41
41
  In the following example we replace the placeholder with a concrete port for
42
- creating a user.
42
+ managing course enrollments.
43
43
 
44
- ```ts title="users/example-ports.ts"
45
- export interface User {
46
- id: string
47
- email: string
48
- active: boolean | null
49
- }
44
+ ```ts title="enrollment/example-ports.ts"
45
+ import { InMemoryDatabaseDriver } from './application/database.ts'
46
+ import { Course } from './domain/courses.ts'
47
+ import { Student } from './domain/students.ts'
48
+ import { InscriptionAggregate } from './domain/inscriptions.ts'
50
49
 
51
- export class UsersRegistry {
52
- public async createUser(user: User): Promise<User> {
53
- // ...
50
+ const database: Record<string, Record<string, unknown>[]> = {}
51
+
52
+ export class Example {
53
+ [property: string]: unknown
54
+
55
+ private readonly driver: InMemoryDatabaseDriver
56
+
57
+ constructor() {
58
+ this.driver = new InMemoryDatabaseDriver(database)
59
+ }
60
+
61
+ public createStudent(student: Student) {
62
+ const result = this.driver.connect('students').create(student.toJSON())
63
+ this.driver.disconnect()
64
+ return result
65
+ }
66
+
67
+ public createCourse(course: Course) {
68
+ const result = this.driver.connect('courses').create(course.toJSON())
69
+ this.driver.disconnect()
70
+ return result
54
71
  }
55
- }
56
- ```
57
72
 
58
- `User` expresses the user data that crosses the boundary. `UsersRegistry` is a
59
- boundary object of the context and `createUser()` is one concrete capability
60
- that it exposes.
73
+ public listInscriptions() {
74
+ const result = this.driver.connect('inscriptions').all()
75
+ this.driver.disconnect()
76
+ return result
77
+ }
61
78
 
62
- This definition focuses on the interaction the context makes available. The
63
- port keeps its identity as one boundary capability while making its data and
64
- action explicit.
79
+ public createInscription(student: Student, course: Course) {
80
+ const inscription = InscriptionAggregate.enroll(student, course)
81
+ const result = this.driver.connect('inscriptions').create(inscription.toJSON())
82
+ this.driver.disconnect()
83
+ return result
84
+ }
85
+ }
86
+ ```
65
87
 
66
- The port is the executable object that this context exposes. Types help
67
- describe it and make its boundary explicit.
88
+ `Example` is a boundary object of the context. Each static method is one
89
+ concrete capability it exposes. The port connects the boundary to a driver,
90
+ uses domain concepts internally, and keeps infrastructure details out of the
91
+ caller.
68
92
 
69
93
  This is the normal flow inside the context boundary:
70
94
 
@@ -83,10 +107,23 @@ capability and uses domain capabilities.
83
107
 
84
108
  As the context grows, you can keep several port modules at the context root.
85
109
 
86
- ```ts title="users/registry.ts"
87
- export class UsersRegistry {
88
- public async listUsers(): Promise<User[]> {
89
- // ...
110
+ ```ts title="enrollment/courses.ts"
111
+ import { CoursesService } from './application/services.ts'
112
+ import { Course } from './domain/courses.ts'
113
+
114
+ export class CoursesPort {
115
+ constructor(private readonly service: CoursesService) {}
116
+
117
+ public all(): Record<string, unknown>[] {
118
+ return this.service.all()
119
+ }
120
+
121
+ public create(name: string, description: string, hours: number): boolean {
122
+ return this.service.create(new Course(name, description, hours))
123
+ }
124
+
125
+ public delete(course: Course): boolean {
126
+ return this.service.delete(course)
90
127
  }
91
128
  }
92
129
  ```
@@ -110,9 +147,10 @@ in the generated folders.
110
147
 
111
148
  ```mermaid
112
149
  flowchart TD
113
- users["users/"] --> registry["registry.ts"]
114
- users --> application["application/"]
115
- users --> domain["domain/"]
150
+ enrollment["enrollment/"] --> examplePort["example-ports.ts"]
151
+ enrollment --> courses["courses.ts"]
152
+ enrollment --> application["application/"]
153
+ enrollment --> domain["domain/"]
116
154
  ```
117
155
 
118
156
  This layout keeps the context boundary visible from the top level. It also
@@ -63,21 +63,13 @@ contracts that adapters and services can share.
63
63
  #### Shared HTTP Files
64
64
 
65
65
  The `shared/application/http` directory groups the framework-agnostic HTTP
66
- boundary and the type-only specifications for common web content formats.
66
+ boundary contracts.
67
67
 
68
68
  | File | Responsibility |
69
69
  | --- | --- |
70
- | `shared/application/http/handlers.ts` | Declares `HttpRequestHandler` and `HttpMiddleware`. |
71
70
  | `shared/application/http/errors.ts` | Declares `HttpError`. |
72
- | `shared/application/http/json-api.ts` | Type-only JSON:API v1.1 document, resource, and Atomic Operations declarations. |
73
- | `shared/application/http/json-web-token.ts` | Type-only JOSE/JWT declarations (JWK, JWS, JWE, JWT claims). |
74
- | `shared/application/http/opengraph.ts` | Type-only Open Graph, Twitter Card, and social metadata declarations. |
75
71
 
76
- `handlers.ts` and `errors.ts` are the only files in this directory with
77
- runtime code. `json-api.ts`, `json-web-token.ts`, and `opengraph.ts` contain
78
- compile-time structure only; they describe the shape of external formats
79
- without implementing parsing, validation, or serialization. Each file is
80
- documented in its own page under `shared/application/http/*.md`.
72
+ `errors.ts` is documented in its own page under `shared/application/http/errors.md`.
81
73
 
82
74
  #### Shared Data Files
83
75
 
@@ -85,8 +77,9 @@ The generated template also includes a small set of data-access abstractions.
85
77
 
86
78
  | File | Responsibility |
87
79
  | --- | --- |
80
+ | `shared/application/data/capabilities.ts` | Declares the operation capability interfaces: `Listable`, `Filterable`, `Sortable`, `Creatable`, `Updatable`, `Deletable`, `Aggregatable`, and `Relatable`. |
88
81
  | `shared/application/data/drivers.ts` | Declares `DriverAdapter`, the connection contract with a data source. |
89
- | `shared/application/data/managers.ts` | Declares `DataManager`, `DatasetManager`, and plain-record operations. |
82
+ | `shared/application/data/managers.ts` | Declares `DataManager` and `DatasetManager`. |
90
83
  | `shared/application/data/repositories.ts` | Declares `Repository`, which transforms raw records into domain representations. |
91
84
 
92
85
  These contracts belong to the application layer because they define how the
@@ -18,7 +18,7 @@ flowchart TD
18
18
  root["Library root"] --> types["types/"]
19
19
  root --> main["main.ts"]
20
20
  root --> shared["shared/"]
21
- root --> users["users/"]
21
+ root --> enrollment["enrollment/"]
22
22
  ```
23
23
 
24
24
  `types/` groups the root-level type declarations. `main.ts` starts as a
@@ -69,7 +69,7 @@ capability.
69
69
 
70
70
  ```mermaid
71
71
  flowchart TD
72
- contexts["Contexts"] --> users["users/"]
72
+ contexts["Contexts"] --> enrollment["enrollment/"]
73
73
  contexts --> billing["billing/"]
74
74
  contexts --> inventory["inventory/"]
75
75
  contexts --> sales["sales/"]
@@ -85,10 +85,10 @@ Every generated context starts with the same internal structure.
85
85
 
86
86
  ```mermaid
87
87
  flowchart TD
88
- users["users/"] --> ports["example-ports.ts"]
89
- users --> adapters["adapters/"]
90
- users --> application["application/"]
91
- users --> domain["domain/"]
88
+ enrollment["enrollment/"] --> ports["example-ports.ts"]
89
+ enrollment --> adapters["adapters/"]
90
+ enrollment --> application["application/"]
91
+ enrollment --> domain["domain/"]
92
92
  ```
93
93
 
94
94
  `example-ports.ts` is an example module in the root communication surface of
@@ -189,6 +189,23 @@ application services use domain capabilities, while adapters can depend on
189
189
  ports and third-party libraries. The port branch stops at the boundary because
190
190
  what exists beyond that port depends on the system that implements it.
191
191
 
192
+ ```ts title="main.ts"
193
+ import { Example } from './enrollment/example-ports.ts'
194
+ import { Student } from './enrollment/domain/students.ts'
195
+ import { Course } from './enrollment/domain/courses.ts'
196
+ import { Email } from './shared/domain/value-objects.ts'
197
+
198
+ const example = new Example()
199
+ const student = new Student('Ada Lovelace', Email.from('ada@example.com'))
200
+ const course = new Course('Mathematics', 'Fundamentals of algebra and calculus', 40)
201
+
202
+ example.createStudent(student)
203
+ example.createCourse(course)
204
+ example.createInscription(student, course)
205
+
206
+ console.log(example.listInscriptions())
207
+ ```
208
+
192
209
  #### Next Step
193
210
 
194
211
  Use this structure as the default layout for new code. When you need to inspect
@@ -1,188 +1,260 @@
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 three files:
8
+ The generated structure splits this concern into four files:
10
9
 
11
- 1. `drivers.ts` for connection adapters;
12
- 2. `managers.ts` for plain-record operations;
13
- 3. `repositories.ts` for record-to-domain transformation.
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
- #### Driver Adapter
15
+ #### Capabilities
16
16
 
17
- `DriverAdapter` is responsible for connecting to a data source and returning an
18
- enabled `DataManager`.
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/drivers.ts"
21
- import { DataManager } from './managers.js'
20
+ ```ts title="shared/application/data/capabilities.ts"
21
+ export interface Listable<DataShape extends Generic = Generic> {
22
+ all(): DataShape[]
23
+ }
22
24
 
23
- export abstract class DriverAdapter<M extends DataManager = DataManager> {
24
- public abstract connect(...args: unknown[]): Promise<M>
25
+ export interface Creatable<DataShape extends Generic = Generic, Feedback = unknown> {
26
+ create(data: DataShape): Feedback
27
+ }
25
28
 
26
- public abstract disconnect(): Promise<unknown>
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
- The `connect()` method returns a manager that can read or manipulate raw data.
31
- The `disconnect()` method closes the interaction when the work is finished.
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 responsible for exposing plain source data.
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
- public none(): Array<T> {
40
- return []
41
- }
93
+ [property: string]: unknown
94
+ }
95
+
96
+ export abstract class DatasetManager<T = Record<string, unknown>> extends DataManager<T> {
97
+ [property: string]: unknown
42
98
 
43
- public abstract all(): Promise<Array<T>>
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
- The base class provides `none()` as an explicit empty result and requires
48
- `all()` for retrieving records. The generated template also includes operation
49
- contracts such as `Filterable`, `Creatable`, and `Updatable`, plus the
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
- #### Implementation
111
+ #### Driver Adapter
112
+
113
+ `DriverAdapter` is responsible for connecting to a data source and returning an
114
+ enabled `DataManager`.
53
115
 
54
- In the following example we implement an in-memory manager and its driver.
116
+ ```ts title="shared/application/data/drivers.ts"
117
+ export abstract class DriverAdapter<M extends DataManager = DataManager> {
118
+ [property: string]: unknown
55
119
 
56
- ```ts title="users/adapters/memory-users-driver.ts"
57
- import { DriverAdapter } from '../../shared/application/data/drivers.js'
58
- import { DataManager } from '../../shared/application/data/managers.js'
120
+ public abstract connect(...args: unknown[]): M
59
121
 
60
- type UserRecord = {
61
- id: string
62
- email: string
63
- active: boolean | null
122
+ public abstract disconnect(): unknown
64
123
  }
124
+ ```
65
125
 
66
- class MemoryUsersManager extends DataManager<UserRecord> {
67
- public constructor(private readonly rows: Array<UserRecord>) {
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
- public async all(): Promise<Array<UserRecord>> {
72
- return this.rows
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>
75
134
 
76
- export class MemoryUsersDriver extends DriverAdapter<MemoryUsersManager> {
77
- public constructor(private readonly rows: Array<UserRecord>) {
135
+ export type Database = Record<string, Generic[]>
136
+
137
+ export class InMemoryDatabaseDriver extends DriverAdapter<InMemoryDatabaseManager> {
138
+ [property: string]: unknown
139
+
140
+ private manager: InMemoryDatabaseManager | null = null
141
+
142
+ constructor(private readonly database: Database) {
78
143
  super()
79
144
  }
80
145
 
81
- public async connect(): Promise<MemoryUsersManager> {
82
- return new MemoryUsersManager(this.rows)
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 async disconnect(): Promise<void> {
86
- return undefined
155
+ public disconnect(): void {
156
+ this.manager = null
87
157
  }
88
158
  }
89
159
  ```
90
160
 
91
- `MemoryUsersDriver` owns the connection contract. `MemoryUsersManager` owns the
92
- raw records. The application layer can use both without knowing whether the
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
- Put all your complex data operations in `DataManager`. `Repository` should only handle the transformation of raw records into domain representations. For example, if you need to relate users to their posts, implement that in a manager:
165
+ #### Repository
96
166
 
97
- ```ts
98
- class ComplexUsersManager extends DataManager<EnrichedUserRecord> {
99
- ...
167
+ `Repository` is responsible for transforming raw records into domain-oriented
168
+ representations. It holds a reference to the manager and requires `transform()`
169
+ to map a raw record into a domain entity.
100
170
 
101
- public async findAllAndRelate(): Promise<Array<EnrichedUserRecord>> {
102
- ...
103
- }
171
+ ```ts title="shared/application/data/repositories.ts"
172
+ export abstract class Repository<
173
+ RawDataShape = Generic,
174
+ EntityShape = Generic,
175
+ M extends DataManager<RawDataShape> = DataManager<RawDataShape>
176
+ > {
177
+ [property: string]: unknown
178
+
179
+ public constructor(public readonly manager: M) {}
180
+
181
+ protected abstract transform(data: RawDataShape, ...args: unknown[]): EntityShape
104
182
  }
105
183
  ```
106
184
 
107
- #### Repository
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`.
108
188
 
109
- `Repository` is responsible for transforming raw records into domain-oriented
110
- representations.
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
+ import { Email } from '../shared/domain/value-objects.ts'
111
197
 
112
- ```ts title="shared/application/data/repositories.ts"
113
- import { type DataManager } from './managers.js'
114
- import { type DriverAdapter } from './drivers.js'
198
+ export class CoursesRepository extends Repository<CourseData, Course, InMemoryDatabaseManager> {
199
+ [property: string]: unknown
115
200
 
116
- export abstract class Repository<
117
- DataShape extends Record<string, unknown> = Record<string, unknown>,
118
- EntityShape extends Record<string, unknown> = Record<string, unknown>
119
- > {
120
- public constructor(public readonly driver: DriverAdapter<DataManager<DataShape>>) {}
201
+ constructor(manager: InMemoryDatabaseManager) {
202
+ super(manager)
203
+ }
121
204
 
122
- public async all(): Promise<Array<EntityShape>> {
123
- const connection = await this.driver.connect()
124
- const raw = await connection.all()
125
- const entities = this.transformList(raw)
126
- await this.driver.disconnect()
127
- return entities
205
+ protected transform(data: CourseData): Course {
206
+ return new Course(data.name, data.description, data.duration_hours)
128
207
  }
129
208
 
130
- protected transformList(data: Array<DataShape>): Array<EntityShape> {
131
- return data.map(this.transform)
209
+ public create(course: Course): boolean {
210
+ return this.manager.create(course.toJSON())
132
211
  }
133
212
 
134
- protected abstract transform(data: DataShape): EntityShape
213
+ public delete(course: Course): boolean {
214
+ return this.manager.delete(course.toJSON())
215
+ }
135
216
  }
136
- ```
137
217
 
138
- The base repository already defines the `all()` flow. A concrete repository only
139
- needs to implement `transform()`.
218
+ export class StudentsRepository extends Repository<StudentData, Student, InMemoryDatabaseManager> {
219
+ [property: string]: unknown
140
220
 
141
- #### Repository Implementation
221
+ constructor(manager: InMemoryDatabaseManager) {
222
+ super(manager)
223
+ }
142
224
 
143
- Now that the driver exists, a repository can translate raw records into a shape
144
- that the rest of the context can use.
225
+ protected transform(data: StudentData): Student {
226
+ return new Student(data.name, Email.from(data.email))
227
+ }
145
228
 
146
- ```ts title="users/adapters/users-repository.ts"
147
- import { Repository } from '../../shared/application/data/repositories.js'
148
- import { DriverAdapter } from '../../shared/application/data/drivers.js'
149
- import { DataManager } from '../../shared/application/data/managers.js'
229
+ public create(student: Student): boolean {
230
+ return this.manager.create(student.toJSON())
231
+ }
150
232
 
151
- type UserRecord = {
152
- id: string
153
- email: string
154
- active: boolean | null
233
+ public delete(student: Student): boolean {
234
+ return this.manager.delete(student.toJSON())
235
+ }
155
236
  }
156
237
 
157
- type UserView = {
158
- id: string
159
- email: string
160
- active: boolean | null
161
- }
238
+ export class InscriptionsRepository extends Repository<InscriptionData, Inscription, InMemoryDatabaseManager> {
239
+ [property: string]: unknown
162
240
 
163
- export class UsersRepository extends Repository<UserRecord, UserView> {
164
- public constructor(driver: DriverAdapter<DataManager<UserRecord>>) {
165
- super(driver)
241
+ constructor(manager: InMemoryDatabaseManager) {
242
+ super(manager)
166
243
  }
167
244
 
168
- protected transform(data: UserRecord): UserView {
169
- return {
170
- id: data.id,
171
- email: data.email,
172
- active: data.active,
173
- }
245
+ protected transform(data: InscriptionData, student: Student, course: Course): Inscription {
246
+ return new Inscription(student, course, data.enrolled_at)
174
247
  }
175
- }
176
- ```
177
248
 
178
- This repository does not own the connection lifecycle because `Repository`
179
- already handles it. Its responsibility is the mapping between raw source data
180
- and the representation used by the context.
249
+ public create(inscription: Inscription): boolean {
250
+ return this.manager.create(inscription.toJSON())
251
+ }
181
252
 
182
- > **Warning**
183
- > The generated `Repository` only implements `all()`. If the project needs
184
- > filtering, creation, or updates, add those operations explicitly instead of
185
- > assuming they already exist in the base class.
253
+ public delete(inscription: Inscription): boolean {
254
+ return this.manager.delete(inscription.toJSON())
255
+ }
256
+ }
257
+ ```
186
258
 
187
259
  #### Example Flow
188
260
 
@@ -191,8 +263,7 @@ The normal flow of the data abstractions is the following:
191
263
  ```mermaid
192
264
  flowchart LR
193
265
  service[Service] --> repository[Repository]
194
- repository --> driver[Driver]
195
- driver --> manager["Data manager"]
266
+ repository --> manager["Data manager"]
196
267
  manager --> raw["Raw records"]
197
268
  repository --> transformed["Transformed records"]
198
269
  transformed --> service