tshex-cli 1.0.19 → 1.0.21

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.
@@ -0,0 +1,165 @@
1
+ ### Context Ports
2
+
3
+ Context ports define the communication available at a context boundary.
4
+ They describe what data enters the context, what data leaves it, and which
5
+ operation another system can invoke.
6
+
7
+ Ports are used to keep communication contracts explicit. An adapter imports a
8
+ port, implements that contract, and translates the external interaction into an
9
+ application process.
10
+
11
+ > **Hint**
12
+ > The generated `example-ports.ts` file is only a placeholder. Replace it when
13
+ > the first real interaction of the context becomes clear.
14
+
15
+ #### Root Port File
16
+
17
+ The generated context starts with a single root file for ports.
18
+
19
+ ```ts title="users/example-ports.ts"
20
+ export function example(): void {
21
+ // ...
22
+ }
23
+ ```
24
+
25
+ This placeholder does not define a real contract yet. Its purpose is to mark
26
+ the context root as the place where boundary-facing types and interfaces live.
27
+
28
+ #### First Contract
29
+
30
+ In the following example we replace the placeholder with a minimal contract for
31
+ creating a user.
32
+
33
+ ```ts title="users/example-ports.ts"
34
+ export type CreateUserRequest = {
35
+ user: {
36
+ id: string
37
+ email: string
38
+ active: boolean | null
39
+ }
40
+ }
41
+
42
+ export type CreateUserResponse = {
43
+ user: {
44
+ id: string
45
+ email: string
46
+ active: boolean | null
47
+ }
48
+ }
49
+
50
+ export interface CreateUserPort {
51
+ create(request: CreateUserRequest): Promise<CreateUserResponse>
52
+ }
53
+ ```
54
+
55
+ `CreateUserRequest` defines the incoming payload. `CreateUserResponse` defines
56
+ the outgoing payload. `CreateUserPort` exposes the operation available at the
57
+ boundary.
58
+
59
+ This contract says nothing about HTTP, queues, or databases. It only declares
60
+ the shape of the communication.
61
+
62
+ #### Adapter Implementation
63
+
64
+ Now that the port exists, an adapter can implement it and delegate the work to
65
+ an application service.
66
+
67
+ ```ts title="users/adapters/create-user.ts"
68
+ import type {
69
+ CreateUserPort,
70
+ CreateUserRequest,
71
+ CreateUserResponse,
72
+ } from '../example-ports.js'
73
+
74
+ type CreateUserService = {
75
+ execute(data: {
76
+ id: string
77
+ email: string
78
+ active: boolean | null
79
+ }): Promise<CreateUserResponse>
80
+ }
81
+
82
+ export class CreateUserAdapter implements CreateUserPort {
83
+ public constructor(
84
+ private readonly service: CreateUserService,
85
+ ) {}
86
+
87
+ public async create(
88
+ request: CreateUserRequest,
89
+ ): Promise<CreateUserResponse> {
90
+ return this.service.execute({
91
+ id: request.user.id,
92
+ email: request.user.email,
93
+ active: request.user.active,
94
+ })
95
+ }
96
+ }
97
+ ```
98
+
99
+ The adapter implements `CreateUserPort`, so it must provide `create()`. Inside
100
+ that method it translates the root-level request into the input expected by the
101
+ application service.
102
+
103
+ This is the normal flow of the generated structure:
104
+
105
+ ```text
106
+ external system -> adapter -> application -> domain
107
+ ```
108
+
109
+ The port belongs to the boundary. The adapter materializes the boundary. The
110
+ application process executes the use case.
111
+
112
+ #### Multiple Port Files
113
+
114
+ As the context grows, you can keep several contracts at the context root.
115
+
116
+ ```ts title="users/list-users.ts"
117
+ export type ListUsersRequest = {
118
+ active: boolean | null
119
+ }
120
+
121
+ export type ListUsersResponse = {
122
+ users: Array<{
123
+ id: string
124
+ email: string
125
+ active: boolean | null
126
+ }>
127
+ }
128
+
129
+ export interface ListUsersPort {
130
+ list(request: ListUsersRequest): Promise<ListUsersResponse>
131
+ }
132
+ ```
133
+
134
+ An adapter can then import the contract from the file that owns it.
135
+
136
+ This arrangement is useful when one context exposes several independent forms
137
+ of communication. A single file works well for a small context. Separate files
138
+ become easier to maintain when responsibilities start to diverge.
139
+
140
+ > **Warning**
141
+ > A port should define communication, not domain behavior. Avoid moving entity
142
+ > rules, repository logic, or infrastructure details into the port file.
143
+
144
+ #### Example Layout
145
+
146
+ The following structure keeps ports at the root while the implementation lives
147
+ in the generated folders.
148
+
149
+ ```text
150
+ users/
151
+ ├── example-ports.ts
152
+ ├── list-users.ts
153
+ ├── adapters/
154
+ ├── application/
155
+ └── domain/
156
+ ```
157
+
158
+ This layout keeps the context boundary visible from the top level. It also
159
+ reduces coupling between adapters because they all import the same contracts.
160
+
161
+ #### Next Step
162
+
163
+ After defining a port, implement the corresponding adapter and connect it to an
164
+ application service. The surrounding structure is described in
165
+ `library-structure.md`.
@@ -0,0 +1,88 @@
1
+ ### Generated File Reference
2
+
3
+ This reference describes the files generated by the library template.
4
+ Use it to identify where a concern belongs before adding new code.
5
+
6
+ #### Root Files
7
+
8
+ The root contains the main entry points of the generated library.
9
+
10
+ | File | Responsibility |
11
+ | --- | --- |
12
+ | `index.d.ts` | Declares root-level shared types such as `Generic<T>`. |
13
+ | `main.ts` | Starts as a placeholder for the main implementation and root exports. |
14
+
15
+ `index.d.ts` is the place for root-level type declarations. `main.ts` is the
16
+ place for the main runtime entry point when the library starts exposing shared
17
+ runtime components.
18
+
19
+ #### Shared Domain Files
20
+
21
+ The `shared/domain` directory contains foundations for domain modeling.
22
+
23
+ | File | Responsibility |
24
+ | --- | --- |
25
+ | `shared/domain/value-objects.ts` | Declares `ValueObject<T>` and provides `Email` and `NullableBoolean`. |
26
+ | `shared/domain/entities.ts` | Declares the `Entity` base class. |
27
+ | `shared/domain/aggregates.ts` | Declares the `Aggregate` base class. |
28
+ | `shared/domain/errors.ts` | Declares `ValueError`. |
29
+
30
+ These files contain reusable domain concepts whose meaning can be shared across
31
+ contexts. They are discussed in more detail in `shared/domain/*.md`.
32
+
33
+ #### Shared Application Files
34
+
35
+ The `shared/application` directory contains contracts used to coordinate use
36
+ cases and integrations.
37
+
38
+ | File | Responsibility |
39
+ | --- | --- |
40
+ | `shared/application/validations.ts` | Declares the `Validatable` contract. |
41
+ | `shared/application/services.ts` | Declares the `Service` base class for use cases. |
42
+ | `shared/application/http.ts` | Declares framework-agnostic HTTP contracts. |
43
+ | `shared/application/loggers.ts` | Declares shared log levels and the `Logger` contract. |
44
+ | `shared/application/events.ts` | Declares `Event`, `EventHandler`, and `EventDispatcher`. |
45
+
46
+ These files do not implement frameworks or transports. They define the stable
47
+ contracts that adapters and services can share.
48
+
49
+ #### Shared Data Files
50
+
51
+ The generated template also includes a small set of data-access abstractions.
52
+
53
+ | File | Responsibility |
54
+ | --- | --- |
55
+ | `shared/application/data/drivers.ts` | Declares `DriverAdapter`, the connection contract with a data source. |
56
+ | `shared/application/data/managers.ts` | Declares `DataManager`, `DatasetManager`, and plain-record operations. |
57
+ | `shared/application/data/repositories.ts` | Declares `Repository`, which transforms raw records into domain representations. |
58
+
59
+ These contracts belong to the application layer because they define how the
60
+ application coordinates data access without forcing a specific driver.
61
+
62
+ #### Context Files
63
+
64
+ Each generated context starts with a root port file and three directories.
65
+
66
+ | Path | Responsibility |
67
+ | --- | --- |
68
+ | `users/example-ports.ts` | Placeholder root file for context ports. |
69
+ | `users/domain/` | Domain capabilities and rules for the context. |
70
+ | `users/application/` | Processes that use domain capabilities to fulfill system purposes. |
71
+ | `users/adapters/` | Integrations that implement ports and connect the context to external systems. |
72
+
73
+ The `users/` path is an example context name. Your project can generate one or
74
+ more contexts with the same internal layout.
75
+
76
+ #### How To Use This Reference
77
+
78
+ Use the following sequence when deciding where new code belongs.
79
+
80
+ 1. Put reusable domain concepts in `shared/domain`.
81
+ 2. Put reusable application contracts in `shared/application`.
82
+ 3. Put context-specific rules in `<context>/domain`.
83
+ 4. Put use cases in `<context>/application`.
84
+ 5. Put boundary implementations in `<context>/adapters`.
85
+ 6. Put boundary contracts at the context root.
86
+
87
+ This reference explains placement. The architectural rationale is described in
88
+ `library-structure.md`.
@@ -0,0 +1,163 @@
1
+ ### Library Structure
2
+
3
+ The library structure organizes generated code into root files, shared
4
+ capabilities, and one or more contexts.
5
+ Each level has a distinct responsibility so that domain rules, use cases, and
6
+ integrations do not collapse into the same place.
7
+
8
+ This structure is used to keep the implementation centered on capabilities.
9
+ Shared code holds common abstractions. Contexts hold application-specific
10
+ language, rules, and operations.
11
+
12
+ #### Root
13
+
14
+ The root contains the entry points of the generated library.
15
+
16
+ ```text
17
+ index.d.ts
18
+ main.ts
19
+ shared/
20
+ users/
21
+ ```
22
+
23
+ `index.d.ts` defines root-level types. `main.ts` starts as a placeholder for
24
+ main runtime exports. The rest of the structure lives under `shared/` and one
25
+ or more context directories.
26
+
27
+ #### Shared
28
+
29
+ The `shared` directory contains concepts that can be reused by multiple
30
+ contexts.
31
+
32
+ ```text
33
+ shared/
34
+ ├── application/
35
+ └── domain/
36
+ ```
37
+
38
+ `shared/domain` contains modeling foundations such as value objects, entities,
39
+ aggregates, and errors. `shared/application` contains contracts for services,
40
+ validations, events, logging, HTTP boundaries, and data access.
41
+
42
+ Move code to `shared` only when its meaning belongs to more than one context.
43
+ Until then, keep it close to the context that owns the rule.
44
+
45
+ #### Contexts
46
+
47
+ A context groups the vocabulary, rules, and operations of one application
48
+ capability.
49
+
50
+ ```text
51
+ users/
52
+ billing/
53
+ inventory/
54
+ sales/
55
+ ```
56
+
57
+ Each context can evolve independently while still reusing the abstractions from
58
+ `shared`. This separation reduces accidental coupling between unrelated parts of
59
+ the system.
60
+
61
+ #### Context Layout
62
+
63
+ Every generated context starts with the same internal structure.
64
+
65
+ ```text
66
+ users/
67
+ ├── example-ports.ts
68
+ ├── adapters/
69
+ ├── application/
70
+ └── domain/
71
+ ```
72
+
73
+ `example-ports.ts` is the root communication surface of the context.
74
+ `domain/` contains capabilities and rules. `application/` contains processes.
75
+ `adapters/` contains boundary implementations.
76
+
77
+ #### Domain
78
+
79
+ The domain layer contains the context's capabilities.
80
+
81
+ This layer models concepts that carry business meaning: value objects, entities,
82
+ aggregates, and the rules that make them valid. The domain should not depend on
83
+ transport concerns or infrastructure details.
84
+
85
+ Typical domain responsibilities include validating an email address, identifying
86
+ an entity, changing the state of an order, or grouping related entities into a
87
+ single unit.
88
+
89
+ #### Application
90
+
91
+ The application layer contains processes that use domain capabilities to
92
+ fulfill a system purpose.
93
+
94
+ A service in this layer coordinates collaborators. It can validate input,
95
+ construct domain objects, read or write data through abstractions, publish an
96
+ event, and log the result.
97
+
98
+ The application layer is responsible for orchestration, not for owning the
99
+ business rules themselves.
100
+
101
+ #### Adapters
102
+
103
+ The adapters layer contains the integrations that connect a context to other
104
+ systems.
105
+
106
+ An adapter can expose an HTTP handler, consume a message, call a remote API,
107
+ implement a data driver, or connect to an event bus. Its role is to translate
108
+ external input or output into the contracts expected by the application layer.
109
+
110
+ #### Ports
111
+
112
+ Ports define the communication available at the context boundary.
113
+
114
+ They live at the context root because they describe how the context is used
115
+ from the outside. An adapter imports a port, implements it, and delegates the
116
+ work to an application process.
117
+
118
+ The generated template starts with `example-ports.ts`, but a larger context may
119
+ split ports across several files. The detailed guidance for that layout lives in
120
+ `context-ports.md`.
121
+
122
+ #### Dependency Direction
123
+
124
+ The normal dependency direction is the following:
125
+
126
+ ```text
127
+ adapter -> port
128
+ adapter -> application
129
+ application -> domain
130
+ ```
131
+
132
+ This direction keeps the core model isolated from transport and infrastructure
133
+ details. The deeper a layer is, the less it should know about the outside.
134
+
135
+ > **Warning**
136
+ > Avoid importing adapter-specific concerns into the domain layer. Once a domain
137
+ > object depends on HTTP, database, or framework details, the context boundary
138
+ > becomes harder to change.
139
+
140
+ #### Example Flow
141
+
142
+ The following diagram shows the runtime flow of a typical operation.
143
+
144
+ ```text
145
+ External system
146
+ |
147
+ v
148
+ Port + adapter
149
+ |
150
+ v
151
+ Application service
152
+ |
153
+ v
154
+ Domain capability
155
+ ```
156
+
157
+ The adapter receives the input, the application service coordinates the use
158
+ case, and the domain provides the rules and behavior required by that use case.
159
+
160
+ #### Next Step
161
+
162
+ Use this structure as the default layout for new code. When you need to inspect
163
+ the purpose of a generated file, consult `generated-file-reference.md`.
@@ -0,0 +1,70 @@
1
+ ### Library Types
2
+
3
+ The generated root type `Generic<T>` represents a plain object whose keys are
4
+ strings and whose values share the same type.
5
+ It provides a small common building block for code that works with object-like
6
+ data but does not need a more specific shape yet.
7
+
8
+ #### Root Declaration
9
+
10
+ The root declaration lives in `index.d.ts`.
11
+
12
+ ```ts title="index.d.ts"
13
+ type Generic<T = unknown> = Record<string, T>
14
+ ```
15
+
16
+ This alias expands to `Record<string, T>`. When no type argument is provided,
17
+ the values use `unknown`.
18
+
19
+ #### Basic Usage
20
+
21
+ In the following example we use `Generic<string>` for a set of plain filters.
22
+
23
+ ```ts
24
+ const filters: Generic<string> = {
25
+ status: 'active',
26
+ sort: 'email',
27
+ }
28
+ ```
29
+
30
+ `filters` can only store string values because the type argument fixes the
31
+ value shape for the whole object.
32
+
33
+ #### Default Type Argument
34
+
35
+ Now consider the same pattern without providing a type argument.
36
+
37
+ ```ts
38
+ const metadata: Generic = {
39
+ retries: 2,
40
+ cached: true,
41
+ }
42
+ ```
43
+
44
+ In this case the values use `unknown`. This is useful when the object is plain
45
+ and open-ended, but the caller must narrow each value before using it in a
46
+ specific way.
47
+
48
+ #### When To Use It
49
+
50
+ Use `Generic<T>` when the code needs a simple object contract and the exact set
51
+ of keys is not the main concern.
52
+
53
+ Typical uses include:
54
+
55
+ 1. filter objects;
56
+ 2. metadata objects;
57
+ 3. plain configuration maps;
58
+ 4. transport-neutral dictionaries.
59
+
60
+ When the object has a stable business meaning, prefer a named type instead of a
61
+ generic record.
62
+
63
+ > **Hint**
64
+ > `Generic<T>` is intentionally small. It should support loose object contracts,
65
+ > not replace explicit domain or application types.
66
+
67
+ #### Next Step
68
+
69
+ For the rest of the generated shared abstractions, continue with the pages in
70
+ `shared/application` and `shared/domain`.
@@ -0,0 +1,185 @@
1
+ ### Data
2
+
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
6
+ transformation so that a context can change drivers without rewriting its use
7
+ cases.
8
+
9
+ The generated structure splits this concern into three files:
10
+
11
+ 1. `drivers.ts` for connection adapters;
12
+ 2. `managers.ts` for plain-record operations;
13
+ 3. `repositories.ts` for record-to-domain transformation.
14
+
15
+ #### Driver Adapter
16
+
17
+ `DriverAdapter` is responsible for connecting to a data source and returning an
18
+ enabled `DataManager`.
19
+
20
+ ```ts title="shared/application/data/drivers.ts"
21
+ import { DataManager } from './managers.js'
22
+
23
+ export abstract class DriverAdapter<M extends DataManager = DataManager> {
24
+ public abstract connect(...args: unknown[]): Promise<M>
25
+
26
+ public abstract disconnect(): Promise<unknown>
27
+ }
28
+ ```
29
+
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.
32
+
33
+ #### Data Manager
34
+
35
+ `DataManager` is responsible for exposing plain source data.
36
+
37
+ ```ts title="shared/application/data/managers.ts"
38
+ export abstract class DataManager<T = Record<string, unknown>> {
39
+ public none(): Array<T> {
40
+ return []
41
+ }
42
+
43
+ public abstract all(): Promise<Array<T>>
44
+ }
45
+ ```
46
+
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.
51
+
52
+ #### First Implementation
53
+
54
+ In the following example we implement an in-memory manager and its driver.
55
+
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'
59
+
60
+ type UserRecord = {
61
+ id: string
62
+ email: string
63
+ active: boolean | null
64
+ }
65
+
66
+ class MemoryUsersManager extends DataManager<UserRecord> {
67
+ public constructor(private readonly rows: Array<UserRecord>) {
68
+ super()
69
+ }
70
+
71
+ public async all(): Promise<Array<UserRecord>> {
72
+ return this.rows
73
+ }
74
+ }
75
+
76
+ export class MemoryUsersDriver extends DriverAdapter<MemoryUsersManager> {
77
+ public constructor(private readonly rows: Array<UserRecord>) {
78
+ super()
79
+ }
80
+
81
+ public async connect(): Promise<MemoryUsersManager> {
82
+ return new MemoryUsersManager(this.rows)
83
+ }
84
+
85
+ public async disconnect(): Promise<void> {
86
+ return undefined
87
+ }
88
+ }
89
+ ```
90
+
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.
94
+
95
+ #### Repository
96
+
97
+ `Repository` is responsible for transforming raw records into domain-oriented
98
+ representations.
99
+
100
+ ```ts title="shared/application/data/repositories.ts"
101
+ import { type DataManager } from './managers.js'
102
+ import { type DriverAdapter } from './drivers.js'
103
+
104
+ export abstract class Repository<
105
+ DataShape extends Record<string, unknown> = Record<string, unknown>,
106
+ EntityShape extends Record<string, unknown> = Record<string, unknown>
107
+ > {
108
+ public constructor(public readonly driver: DriverAdapter<DataManager<DataShape>>) {}
109
+
110
+ public async all(): Promise<Array<EntityShape>> {
111
+ const connection = await this.driver.connect()
112
+ const raw = await connection.all()
113
+ const entities = this.transformList(raw)
114
+ await this.driver.disconnect()
115
+ return entities
116
+ }
117
+
118
+ protected transformList(data: Array<DataShape>): Array<EntityShape> {
119
+ return data.map(this.transform)
120
+ }
121
+
122
+ protected abstract transform(data: DataShape): EntityShape
123
+ }
124
+ ```
125
+
126
+ The base repository already defines the `all()` flow. A concrete repository only
127
+ needs to implement `transform()`.
128
+
129
+ #### Repository Implementation
130
+
131
+ Now that the driver exists, a repository can translate raw records into a shape
132
+ that the rest of the context can use.
133
+
134
+ ```ts title="users/adapters/users-repository.ts"
135
+ import { Repository } from '../../shared/application/data/repositories.js'
136
+ import { DriverAdapter } from '../../shared/application/data/drivers.js'
137
+ import { DataManager } from '../../shared/application/data/managers.js'
138
+
139
+ type UserRecord = {
140
+ id: string
141
+ email: string
142
+ active: boolean | null
143
+ }
144
+
145
+ type UserView = {
146
+ id: string
147
+ email: string
148
+ active: boolean | null
149
+ }
150
+
151
+ export class UsersRepository extends Repository<UserRecord, UserView> {
152
+ public constructor(driver: DriverAdapter<DataManager<UserRecord>>) {
153
+ super(driver)
154
+ }
155
+
156
+ protected transform(data: UserRecord): UserView {
157
+ return {
158
+ id: data.id,
159
+ email: data.email,
160
+ active: data.active,
161
+ }
162
+ }
163
+ }
164
+ ```
165
+
166
+ This repository does not own the connection lifecycle because `Repository`
167
+ already handles it. Its responsibility is the mapping between raw source data
168
+ and the representation used by the context.
169
+
170
+ > **Warning**
171
+ > The generated `Repository` only implements `all()`. If the project needs
172
+ > filtering, creation, or updates, add those operations explicitly instead of
173
+ > assuming they already exist in the base class.
174
+
175
+ #### Example Flow
176
+
177
+ The normal flow of the data abstractions is the following:
178
+
179
+ ```text
180
+ service -> repository -> driver -> data manager -> raw records
181
+ service <- repository <- transformed records
182
+ ```
183
+
184
+ This separation keeps the application service focused on orchestration while
185
+ the repository focuses on transformation.