tshex-cli 1.0.24 → 1.0.26

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 CHANGED
@@ -45,7 +45,7 @@ function executeCreateReactContext(templatesDir, contextDir) {
45
45
  console.error(err);
46
46
  }
47
47
  }
48
- function executeCreateTests(sourceDir, destinationDir, fileContents, ignoredSourceDir) {
48
+ function executeCreateTests(sourceDir, destinationDir, fileContents, ignoredSourceDir, rootSourceDir = sourceDir) {
49
49
  const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
50
50
  if (fs.existsSync(destinationDir) === false) {
51
51
  fs.mkdirSync(destinationDir, { recursive: true });
@@ -54,7 +54,7 @@ function executeCreateTests(sourceDir, destinationDir, fileContents, ignoredSour
54
54
  const sourcePath = path.join(sourceDir, entry.name);
55
55
  const destinationPath = path.join(destinationDir, entry.name);
56
56
  if (entry.isDirectory()) {
57
- if (entry.name === 'shared') {
57
+ if (entry.name === 'shared' && sourceDir === rootSourceDir) {
58
58
  continue;
59
59
  }
60
60
  if (ignoredSourceDir !== undefined && sourcePath.startsWith(ignoredSourceDir)) {
@@ -63,7 +63,7 @@ function executeCreateTests(sourceDir, destinationDir, fileContents, ignoredSour
63
63
  if (fs.existsSync(destinationPath) && fs.statSync(destinationPath).isDirectory() === false) {
64
64
  continue;
65
65
  }
66
- executeCreateTests(sourcePath, destinationPath, fileContents, ignoredSourceDir);
66
+ executeCreateTests(sourcePath, destinationPath, fileContents, ignoredSourceDir, rootSourceDir);
67
67
  continue;
68
68
  }
69
69
  if (entry.isFile() && entry.name.endsWith('.ts') && fs.existsSync(destinationPath) === false) {
@@ -4,10 +4,10 @@ Context ports define the communication available at a context boundary.
4
4
  They describe which concrete capability a context exposes, what data enters
5
5
  that capability, and what data it returns.
6
6
 
7
- In practice a port is usually not just an abstraction or a contract. It is a
8
- specific boundary element of the system with identity: a command handler, a
9
- query entry point, an event consumer, a published endpoint, or another concrete
10
- interaction mechanism that exists because the running system exposes it.
7
+ In practice a port is a specific boundary element of the system with identity:
8
+ a command handler, a query entry point, an event consumer, a published
9
+ endpoint, or another concrete interaction mechanism that exists because the
10
+ running system exposes it.
11
11
 
12
12
  Types and interfaces still matter, but they are secondary. Their role is to
13
13
  make the port explicit. The main concern is the port as a real executable
@@ -22,13 +22,19 @@ surface that another actor can call or observe.
22
22
  The generated context starts with a single root file for ports.
23
23
 
24
24
  ```ts title="users/example-ports.ts"
25
- export function example(): void {
26
- // ...
25
+ export class ExamplePort {
26
+ public doSomething(): void {
27
+ // ...
28
+ }
27
29
  }
28
30
  ```
29
31
 
30
- This placeholder does not define a real port yet. Its purpose is to mark the
31
- context root as the place where boundary-facing capabilities are declared.
32
+ This placeholder marks the context root as the place where boundary-facing
33
+ capabilities are declared. Replace it with the first module that defines a real
34
+ port of the context when that capability becomes clear.
35
+
36
+ Context root `.ts` files belong to this same boundary surface. Each one is
37
+ expected to be a module that defines one or more context ports.
32
38
 
33
39
  #### First Port
34
40
 
@@ -36,113 +42,56 @@ In the following example we replace the placeholder with a concrete port for
36
42
  creating a user.
37
43
 
38
44
  ```ts title="users/example-ports.ts"
39
- export type CreateUserRequest = {
40
- user: {
41
- id: string
42
- email: string
43
- active: boolean | null
44
- }
45
+ export interface User {
46
+ id: string
47
+ email: string
48
+ active: boolean | null
45
49
  }
46
50
 
47
- export type CreateUserResponse = {
48
- user: {
49
- id: string
50
- email: string
51
- active: boolean | null
51
+ export class UsersRegistry {
52
+ public async createUser(user: User): Promise<User> {
53
+ // ...
52
54
  }
53
55
  }
54
-
55
- export interface CreateUserPort {
56
- create(request: CreateUserRequest): Promise<CreateUserResponse>
57
- }
58
56
  ```
59
57
 
60
- `CreateUserRequest` defines the incoming payload. `CreateUserResponse` defines
61
- the outgoing payload. `CreateUserPort` names the concrete capability exposed at
62
- the boundary: creating a user.
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.
63
61
 
64
- This definition does not commit to HTTP, queues, or databases. It focuses on
65
- the interaction the context makes available. The transport can vary, but the
66
- port remains the same identifiable boundary capability.
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.
67
65
 
68
- #### Adapter Implementation
66
+ The port is the executable object that this context exposes. Types help
67
+ describe it and make its boundary explicit.
69
68
 
70
- Now that the port exists, the system can materialize it through an adapter and
71
- delegate the work to an application service.
72
-
73
- ```ts title="users/adapters/create-user.ts"
74
- import type {
75
- CreateUserPort,
76
- CreateUserRequest,
77
- CreateUserResponse,
78
- } from '../example-ports.js'
79
-
80
- type CreateUserService = {
81
- execute(data: {
82
- id: string
83
- email: string
84
- active: boolean | null
85
- }): Promise<CreateUserResponse>
86
- }
87
-
88
- export class CreateUserAdapter implements CreateUserPort {
89
- public constructor(
90
- private readonly service: CreateUserService,
91
- ) {}
92
-
93
- public async create(
94
- request: CreateUserRequest,
95
- ): Promise<CreateUserResponse> {
96
- return this.service.execute({
97
- id: request.user.id,
98
- email: request.user.email,
99
- active: request.user.active,
100
- })
101
- }
102
- }
103
- ```
104
-
105
- The adapter implements `CreateUserPort`, so it materializes the boundary
106
- capability and provides `create()`. Inside that method it translates the
107
- root-level request into the input expected by the application service.
108
-
109
- This is the normal flow of the generated structure:
69
+ This is the normal flow inside the context boundary:
110
70
 
111
71
  ```mermaid
112
72
  flowchart LR
113
- external["External system"] --> adapter[Adapter]
114
- adapter --> port[Port]
73
+ caller["Caller"] --> port[Concrete port]
115
74
  port --> application[Application]
116
75
  application --> domain[Domain]
117
76
  ```
118
77
 
119
- The port belongs to the boundary because it is part of what the context really
120
- exposes. The adapter is one implementation path for that port. The application
121
- process executes the use case and uses domain capabilities.
78
+ The port belongs to the boundary because it is part of what the context
79
+ exposes. The application process executes the use case behind the exposed
80
+ capability and uses domain capabilities.
122
81
 
123
82
  #### Multiple Port Files
124
83
 
125
- As the context grows, you can keep several ports at the context root.
126
-
127
- ```ts title="users/list-users.ts"
128
- export type ListUsersRequest = {
129
- active: boolean | null
130
- }
84
+ As the context grows, you can keep several port modules at the context root.
131
85
 
132
- export type ListUsersResponse = {
133
- users: Array<{
134
- id: string
135
- email: string
136
- active: boolean | null
137
- }>
138
- }
139
-
140
- export interface ListUsersPort {
141
- list(request: ListUsersRequest): Promise<ListUsersResponse>
86
+ ```ts title="users/registry.ts"
87
+ export class UsersRegistry {
88
+ public async listUsers(): Promise<User[]> {
89
+ // ...
90
+ }
142
91
  }
143
92
  ```
144
93
 
145
- An adapter can then import the port definition from the file that owns it.
94
+ Another caller can then import the port from the file that owns it.
146
95
 
147
96
  This arrangement is useful when one context exposes several independent
148
97
  capabilities. A single file works well for a small context. Separate files
@@ -150,9 +99,9 @@ become easier to maintain when each port has its own identity and
150
99
  responsibility.
151
100
 
152
101
  > **Warning**
153
- > A port should define an exposed boundary capability, not domain internals.
154
- > Avoid moving entity rules, repository logic, or infrastructure details into
155
- > the port file.
102
+ > A port should define an exposed boundary capability.
103
+ > Keep business rules, repository logic, and infrastructure details in their
104
+ > corresponding layers.
156
105
 
157
106
  #### Example Layout
158
107
 
@@ -161,16 +110,14 @@ in the generated folders.
161
110
 
162
111
  ```mermaid
163
112
  flowchart TD
164
- users["users/"] --> examplePorts["example-ports.ts"]
165
- users --> listUsers["list-users.ts"]
166
- users --> adapters["adapters/"]
113
+ users["users/"] --> registry["registry.ts"]
167
114
  users --> application["application/"]
168
115
  users --> domain["domain/"]
169
116
  ```
170
117
 
171
118
  This layout keeps the context boundary visible from the top level. It also
172
- reduces coupling between adapters because they all import the same port
173
- definitions for the capabilities the context exposes.
119
+ makes each exposed capability easy to locate because the port modules stay at
120
+ the root of the context.
174
121
 
175
122
  #### Next Step
176
123
 
@@ -9,12 +9,26 @@ The root contains the main entry points of the generated library.
9
9
 
10
10
  | File | Responsibility |
11
11
  | --- | --- |
12
- | `index.d.ts` | Declares root-level shared types such as `Generic<T>`. |
13
12
  | `main.ts` | Starts as a placeholder for the main implementation and root exports. |
14
13
 
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.
14
+ `main.ts` is the place for the main runtime entry point when the library
15
+ starts exposing shared runtime components.
16
+
17
+ #### Types Files
18
+
19
+ The `types/` directory contains root-level ambient type declarations.
20
+
21
+ | File | Responsibility |
22
+ | --- | --- |
23
+ | `types/objects.d.ts` | Declares root-level shared types such as `Generic<T>`. |
24
+ | `types/json.d.ts` | Declares `JsonValue` and the other plain, serializable JSON shapes. |
25
+ | `types/cldr.d.ts` | Declares the `Locale` union from Unicode CLDR. |
26
+ | `types/iana.d.ts` | Declares the `TimeZone` union from the IANA time zone database. |
27
+
28
+ `types/objects.d.ts` and `types/json.d.ts` are the place for general-purpose
29
+ root-level type declarations. `types/cldr.d.ts` and `types/iana.d.ts` are
30
+ generated reference types consumed by other shared contracts, such as
31
+ `shared/application/loggers.ts`.
18
32
 
19
33
  #### Shared Domain Files
20
34
 
@@ -39,13 +53,29 @@ cases and integrations.
39
53
  | --- | --- |
40
54
  | `shared/application/validations.ts` | Declares the `Validatable` contract. |
41
55
  | `shared/application/services.ts` | Declares the `Service` base class for use cases. |
42
- | `shared/application/http.ts` | Declares framework-agnostic HTTP contracts. |
43
56
  | `shared/application/loggers.ts` | Declares shared log levels and the `Logger` contract. |
44
57
  | `shared/application/events.ts` | Declares `Event`, `EventHandler`, and `EventDispatcher`. |
45
58
 
46
59
  These files do not implement frameworks or transports. They define the stable
47
60
  contracts that adapters and services can share.
48
61
 
62
+ #### Shared HTTP Files
63
+
64
+ The `shared/application/http` directory groups the framework-agnostic HTTP
65
+ boundary and the type-only specifications for common web content formats.
66
+
67
+ | File | Responsibility |
68
+ | --- | --- |
69
+ | `shared/application/http/http.ts` | Declares `HttpRequestHandler`, `HttpMiddleware`, and `HttpError`. |
70
+ | `shared/application/http/json-api.ts` | Type-only JSON:API v1.1 document, resource, and Atomic Operations declarations. |
71
+ | `shared/application/http/json-web-token.ts` | Type-only JOSE/JWT declarations (JWK, JWS, JWE, JWT claims). |
72
+ | `shared/application/http/opengraph.ts` | Type-only Open Graph, Twitter Card, and social metadata declarations. |
73
+
74
+ `http.ts` is the only file in this directory with runtime code. `json-api.ts`,
75
+ `json-web-token.ts`, and `opengraph.ts` contain compile-time structure only;
76
+ they describe the shape of external formats without implementing parsing,
77
+ validation, or serialization.
78
+
49
79
  #### Shared Data Files
50
80
 
51
81
  The generated template also includes a small set of data-access abstractions.
@@ -61,14 +91,14 @@ application coordinates data access without forcing a specific driver.
61
91
 
62
92
  #### Context Files
63
93
 
64
- Each generated context starts with a root port file and three directories.
94
+ Each generated context starts with an example port file and three directories.
65
95
 
66
96
  | Path | Responsibility |
67
97
  | --- | --- |
68
- | `users/example-ports.ts` | Placeholder root file for context ports. |
98
+ | `users/example-ports.ts` | Example module for context ports. All context root `.ts` files are expected to define context ports. |
69
99
  | `users/domain/` | Domain capabilities and rules for the context. |
70
100
  | `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. |
101
+ | `users/adapters/` | Integrations that wraps third-party libraries or context ports. |
72
102
 
73
103
  The `users/` path is an example context name. Your project can generate one or
74
104
  more contexts with the same internal layout.
@@ -81,8 +111,8 @@ Use the following sequence when deciding where new code belongs.
81
111
  2. Put reusable application contracts in `shared/application`.
82
112
  3. Put context-specific rules in `<context>/domain`.
83
113
  4. Put use cases in `<context>/application`.
84
- 5. Put boundary implementations in `<context>/adapters`.
85
- 6. Put boundary contracts at the context root.
114
+ 5. Put transport and infrastructure integrations in `<context>/adapters`.
115
+ 6. Put concrete boundary ports in context root `.ts` files.
86
116
 
87
117
  This reference explains placement. The architectural rationale is described in
88
- `library-structure.md`.
118
+ `library-structure.md`.
@@ -15,15 +15,34 @@ The root contains the entry points of the generated library.
15
15
 
16
16
  ```mermaid
17
17
  flowchart TD
18
- root["Library root"] --> index["index.d.ts"]
18
+ root["Library root"] --> types["types/"]
19
19
  root --> main["main.ts"]
20
20
  root --> shared["shared/"]
21
21
  root --> users["users/"]
22
22
  ```
23
23
 
24
- `index.d.ts` defines root-level types. `main.ts` starts as a placeholder for
25
- main runtime exports. The rest of the structure lives under `shared/` and one
26
- or more context directories.
24
+ `types/` groups the root-level type declarations. `main.ts` starts as a
25
+ placeholder for main runtime exports. The rest of the structure lives under
26
+ `shared/` and one or more context directories.
27
+
28
+ #### Types
29
+
30
+ The `types/` directory contains ambient type declarations shared by the whole
31
+ library.
32
+
33
+ ```mermaid
34
+ flowchart TD
35
+ types["types/"] --> typesObjects["objects.d.ts"]
36
+ types --> json["json.d.ts"]
37
+ types --> cldr["cldr.d.ts"]
38
+ types --> iana["iana.d.ts"]
39
+ ```
40
+
41
+ `types/objects.d.ts` defines root-level types such as `Generic<T>`.
42
+ `types/json.d.ts` defines `JsonValue` and the other plain, serializable JSON
43
+ shapes. `types/cldr.d.ts` declares the `Locale` union from Unicode CLDR.
44
+ `types/iana.d.ts` declares the `TimeZone` union from the IANA time zone
45
+ database.
27
46
 
28
47
  #### Shared
29
48
 
@@ -72,9 +91,14 @@ flowchart TD
72
91
  users --> domain["domain/"]
73
92
  ```
74
93
 
75
- `example-ports.ts` is the root communication surface of the context.
94
+ `example-ports.ts` is an example module in the root communication surface of
95
+ the context.
76
96
  `domain/` contains capabilities and rules. `application/` contains processes.
77
- `adapters/` contains boundary implementations.
97
+ `adapters/` contains integrations that wrap third-party libraries or context
98
+ ports.
99
+
100
+ Context root `.ts` files belong to the boundary surface of the context. Each
101
+ one is expected to be a module that defines one or more context ports.
78
102
 
79
103
  #### Domain
80
104
 
@@ -105,21 +129,23 @@ business rules themselves.
105
129
  The adapters layer contains the integrations that connect a context to other
106
130
  systems.
107
131
 
108
- An adapter can expose an HTTP handler, consume a message, call a remote API,
109
- implement a data driver, or connect to an event bus. Its role is to translate
110
- external input or output into the contracts expected by the application layer.
132
+ An adapter wraps a third-party library or a context port so that the context
133
+ can interact with a concrete transport or infrastructure path. An adapter can
134
+ expose an HTTP handler, consume a message, call a remote API, implement a data
135
+ driver, or connect to an event bus.
111
136
 
112
137
  #### Ports
113
138
 
114
139
  Ports define the communication available at the context boundary.
115
140
 
116
141
  They live at the context root because they describe how the context is used
117
- from the outside. An adapter imports a port, implements it, and delegates the
118
- work to an application process.
142
+ from the outside. A port is the concrete object the context exposes. Adapters
143
+ or other callers can use that port object and route work into an application
144
+ process.
119
145
 
120
- The generated template starts with `example-ports.ts`, but a larger context may
121
- split ports across several files. The detailed guidance for that layout lives in
122
- `context-ports.md`.
146
+ The generated template starts with `example-ports.ts`. As the context grows,
147
+ additional context root `.ts` modules can define more ports. The detailed
148
+ guidance for that layout lives in `context-ports.md`.
123
149
 
124
150
  #### Dependency Direction
125
151
 
@@ -130,15 +156,13 @@ flowchart LR
130
156
  adapter[Adapter] --> thirdParty["Third-party library"]
131
157
  adapter --> port[Port]
132
158
  port --> application[Application]
133
- port --> domain[Domain]
134
159
  application --> domain
135
160
  ```
136
161
 
137
162
  This direction keeps the core model isolated from transport and infrastructure
138
163
  details. Adapters integrate with external libraries and context ports. Ports
139
- connect the context boundary to application processes or directly to domain
140
- capabilities when no application orchestration is needed. The deeper a layer
141
- is, the less it should know about the outside.
164
+ connect the context boundary to application processes. The deeper a layer is,
165
+ the less it should know about the outside.
142
166
 
143
167
  > **Warning**
144
168
  > Avoid importing adapter-specific concerns into the domain layer. Once a domain
@@ -7,10 +7,10 @@ data but does not need a more specific shape yet.
7
7
 
8
8
  #### Root Declaration
9
9
 
10
- The root declaration lives in `index.d.ts`.
10
+ The root declaration lives in `types/objects.d.ts`.
11
11
 
12
- ```ts title="index.d.ts"
13
- type Generic<T = unknown> = Record<string, T>
12
+ ```ts title="types/objects.d.ts"
13
+ export type Generic<T = unknown> = Record<string, T>
14
14
  ```
15
15
 
16
16
  This alias expands to `Record<string, T>`. When no type argument is provided,
@@ -21,6 +21,8 @@ the values use `unknown`.
21
21
  In the following example we use `Generic<string>` for a set of plain filters.
22
22
 
23
23
  ```ts
24
+ import { type Generic } from './types/objects.js'
25
+
24
26
  const filters: Generic<string> = {
25
27
  status: 'active',
26
28
  sort: 'email',
@@ -35,6 +37,8 @@ value shape for the whole object.
35
37
  Now consider the same pattern without providing a type argument.
36
38
 
37
39
  ```ts
40
+ import { type Generic } from './types/objects.js'
41
+
38
42
  const metadata: Generic = {
39
43
  retries: 2,
40
44
  cached: true,
@@ -64,6 +68,44 @@ generic record.
64
68
  > `Generic<T>` is intentionally small. It should support loose object contracts,
65
69
  > not replace explicit domain or application types.
66
70
 
71
+ #### JSON Values
72
+
73
+ The generated root also declares a small family of types that describe plain,
74
+ serializable JSON data. They live in `types/json.d.ts`.
75
+
76
+ ```ts title="types/json.d.ts"
77
+ export type JsonPrimitive = string | number | boolean | null
78
+
79
+ export type JsonValue = JsonPrimitive | JsonObject | JsonArray
80
+
81
+ export type JsonArray = readonly JsonValue[]
82
+
83
+ export type JsonObject = {
84
+ readonly [key: string]: JsonValue
85
+ }
86
+ ```
87
+
88
+ `JsonPrimitive` covers the scalar values allowed in JSON. `JsonValue` extends
89
+ that with nested objects and arrays, so it recursively describes any value that
90
+ survives a round trip through `JSON.stringify()`/`JSON.parse()`. `JsonObject`
91
+ and `JsonArray` name the two composite shapes so other declarations can refer
92
+ to them directly instead of repeating the union.
93
+
94
+ ```ts
95
+ import { type JsonValue } from './types/json.js'
96
+
97
+ function toLogPayload(value: JsonValue): string {
98
+ return JSON.stringify(value)
99
+ }
100
+ ```
101
+
102
+ Use `JsonValue` and `JsonObject` when a contract must guarantee its data is
103
+ plain and serializable, such as request payloads, stored metadata, or wire
104
+ formats. Prefer `Generic<T>` instead when the value type is not required to be
105
+ JSON-safe. `shared/application/http/json-api.ts` and
106
+ `shared/application/http/json-web-token.ts` build on these types to describe
107
+ JSON:API documents and JOSE/JWT structures.
108
+
67
109
  #### Next Step
68
110
 
69
111
  For the rest of the generated shared abstractions, continue with the pages in